最近在資料夾比對工具遇到一個需求:要以某個資料夾為基準推算檔案的相對路徑。例如,若基準資料夾為 C:\Folder,則 C:\Folder\SubFolder\Test.txt 的相對路徑為 SubFolder\Test.txt。

類似需求以前加減寫過,說穿了全是字串比對的功夫,靠將路徑前方相同部分移除取得相對路徑。如果相對路徑需支援 ..\..\Test.txt 這種寫法,邏輯會複雜一些,若還再考慮英文大小寫的話...

總之,計算相對路徑說難不難,自己寫瑣碎細節倒也不少。於是我想起前幾天談到的 .NET Uri 類別,恰巧有個 MakeRelativeUri() 方法,研究後找到坐享其成的省事解法。

直接用範例程式展示:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Security;
using System.Text;
using System.Threading.Tasks;

namespace UriTest
{
    class Program
    {
        static void Main(string[] args)
        {
            //標準測試
            Test(@"C:\Folder\Test.txt", @"C:\Folder\");
            Test(@"C:\Folder\SubFolder\Test.txt", @"C:\Folder\");
            //上層資料夾
            Test(@"C:\Folder\Test.txt", @"C:\Folder\SubFolder");
            //相鄰資料夾
            Test(@"C:\Folder\SubFolderA\Test.txt", @"C:\Folder\SubFolderB");
            //忽略大小寫差異
            Test(@"c:\folder\subfolder\Test.txt", @"C:\FOLDER\SUBFOLDER");
            //限定basePath子目錄下,不允許上層或相鄰目錄(安全考量)
            try
            {
                Test(@"C:\Folder\Test.txt", @"C:\Folder\SubFolder", true);
            }
            catch (Exception e)
            {
                Console.WriteLine($"Error: {e.Message}");
            }
            try
            {
                Test(@"C:\Folder\SubFolderA\Test.txt", @"C:\Folder\SubFolderB", true);
            }
            catch (Exception e)
            {
                Console.WriteLine($"Error: {e.Message}");
            }

            Console.ReadLine();
        }

        static void Test(string path, string basePath, bool limitSubfolder = false)
        {
            Console.WriteLine(new string('=', 40));
            Console.WriteLine($"Path: {path}");
            Console.WriteLine($"Base Path: {basePath}");
            Console.WriteLine(
                $"Relative Path: {GetRelativePath(path, basePath, limitSubfolder)}");
        }

        public static string GetRelativePath(string fullPath, string basePath, 
            bool limitSubfolder = false)
        {
            if (!basePath.EndsWith(@"\")) basePath += @"\";
            Uri fp = new Uri(fullPath);
            Uri bp = new Uri(basePath);
            var relPath = bp.MakeRelativeUri(fp).ToString().Replace("/", @"\");
            if (relPath.Contains(@"..\") && limitSubfolder)
                throw new ApplicationException("path must be under basePath!");
            return relPath;
        }
    }
}

結果如下,只需幾行程式搞定,推算"..\"上層目錄、英文大小寫有別都難不倒它。

補充兩個小地方:

  1. Uri 傳回的路徑依循 RFC 規範用"/"作為目錄分隔字元,在 Windows 環境可將"/"置換成"\"以求一致。
  2. 實務上有時會限制檔案必須放在指定目錄下,因此我在 GetRelativePath() 加了 limitSubfoler 參數,以便在跳脫範圍時抛出例外。

Comments

Be the first to post a comment

Post a comment