最近在資料夾比對工具遇到一個需求:要以某個資料夾為基準推算檔案的相對路徑。例如,若基準資料夾為 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;
}
}
}
結果如下,只需幾行程式搞定,推算"..\"上層目錄、英文大小寫有別都難不倒它。
補充兩個小地方:
- Uri 傳回的路徑依循 RFC 規範用"/"作為目錄分隔字元,在 Windows 環境可將"/"置換成"\"以求一致。
- 實務上有時會限制檔案必須放在指定目錄下,因此我在 GetRelativePath() 加了 limitSubfoler 參數,以便在跳脫範圍時抛出例外。