Преобразовать путь к файлу в URI файла?
имеет ли .NET Framework какие-либо методы для преобразования пути (например,"C:whatever.txt"
) в файл URI (например,"file:///C:/whatever.txt"
)?
на
6 ответов
на System.Uri
конструктор имеет возможность анализировать полные пути к файлам и превращать их в пути стиля URI. Таким образом, вы можете просто сделать следующее:
var uri = new System.Uri("c:\foo");
var converted = uri.AbsoluteUri;
кажется, никто не понимает, что ни один из System.Uri
конструкторы правильно обрабатывают определенные пути со знаками процента в них.
new Uri(@"C:\%51.txt").AbsoluteUri;
это дает вам "file:///C:/Q.txt"
вместо "file:///C:/%2551.txt"
.
ни одно из значений устаревшего аргумента dontEscape не имеет никакого значения, и указание UriKind также дает тот же результат. Попытка с UriBuilder также не помогает:
new UriBuilder() { Scheme = Uri.UriSchemeFile, Host = "", Path = @"C:\%51.txt" }.Uri.AbsoluteUri
возвращает "file:///C:/Q.txt"
как хорошо.
как далеко как я могу сказать, структура на самом деле отсутствует какой-либо способ сделать это правильно.
мы можем попробовать это, заменив обратные косые черты прямыми косыми чертами и подать путь к Uri.EscapeUriString
- то есть
new Uri(Uri.EscapeUriString(filePath.Replace(Path.DirectorySeparatorChar, '/'))).AbsoluteUri
это, кажется, работает сначала, но если вы даете ему путь C:\a b.txt
в итоге file:///C:/a%2520b.txt
вместо file:///C:/a%20b.txt
- каким-то образом он решает, что какой-то последовательности должны быть декодированы, но не другие. Теперь мы могли бы просто префикс с "file:///"
сами, однако это не может принимать UNC-пути, такие как \remote\share\foo.txt
в учет - то, что кажется общепринятым в Windows, чтобы превратить их в псевдо-urls формы file://remote/share/foo.txt
, и мы должны принять это во внимание.
EscapeUriString
также есть проблема, что он не избегает '#'
символ. Казалось бы, на данный момент у нас нет другого выбора, кроме как сделать свой собственный метод с нуля. Вот что я предлагаю:--18-->
public static string FilePathToFileUrl(string filePath)
{
StringBuilder uri = new StringBuilder();
foreach (char v in filePath)
{
if ((v >= 'a' && v <= 'z') || (v >= 'A' && v <= 'Z') || (v >= '0' && v <= '9') ||
v == '+' || v == '/' || v == ':' || v == '.' || v == '-' || v == '_' || v == '~' ||
v > '\xFF')
{
uri.Append(v);
}
else if (v == Path.DirectorySeparatorChar || v == Path.AltDirectorySeparatorChar)
{
uri.Append('/');
}
else
{
uri.Append(String.Format("%{0:X2}", (int)v));
}
}
if (uri.Length >= 2 && uri[0] == '/' && uri[1] == '/') // UNC path
uri.Insert(0, "file:");
else
uri.Insert(0, "file:///");
return uri.ToString();
}
это намеренно оставляет + и : unencoded, как это обычно делается в Windows. Он также кодирует только latin1, поскольку Internet Explorer не может понять символы юникода в URL-адресах файлов, если они закодированы.
VB.NET:
Dim URI As New Uri("D:\Development\~AppFolder\Att.gif")
разные выходы:
URI.AbsolutePath -> D:/Development/~AppFolder/Att/1.gif
URI.AbsoluteUri -> file:///D:/Development/~AppFolder/Att/1.gif
URI.OriginalString -> D:\Development\~AppFolder\Att.gif
URI.ToString -> file:///D:/Development/~AppFolder/Att/1.gif
URI.LocalPath -> D:\Development\~AppFolder\Att.gif
один лайнер:
New Uri("D:\Development\~AppFolder\Att.gif").AbsoluteUri
выход:
file:///D:/Development/~AppFolder/Att/1.gif
решения выше не работают на Linux.
использование .NET Core, попытка выполнить new Uri("/home/foo/README.md")
приводит к исключению:
Unhandled Exception: System.UriFormatException: Invalid URI: The format of the URI could not be determined.
at System.Uri.CreateThis(String uri, Boolean dontEscape, UriKind uriKind)
at System.Uri..ctor(String uriString)
...
вам нужно дать CLR некоторые подсказки о том, какой URL у вас есть.
это работает:
Uri fileUri = new Uri(new Uri("file://"), "home/foo/README.md");
...и строка, возвращенная fileUri.ToString()
is "file:///home/foo/README.md"
это работает и на Windows.
new Uri(new Uri("file://"), @"C:\Users\foo\README.md").ToString()
...выдает "file:///C:/Users/foo/README.md"
по крайней мере, в .NET 4.5+ вы также можете сделать:
var uri = new System.Uri("C:\foo", UriKind.Absolute);
UrlCreateFromPath на помощь! Ну, не совсем, так как он не поддерживает расширенные и UNC-форматы пути, но это не так сложно преодолеть:
public static Uri FileUrlFromPath(string path)
{
const string prefix = @"\";
const string extended = @"\?\";
const string extendedUnc = @"\?\UNC\";
const string device = @"\.\";
const StringComparison comp = StringComparison.Ordinal;
if(path.StartsWith(extendedUnc, comp))
{
path = prefix+path.Substring(extendedUnc.Length);
}else if(path.StartsWith(extended, comp))
{
path = prefix+path.Substring(extended.Length);
}else if(path.StartsWith(device, comp))
{
path = prefix+path.Substring(device.Length);
}
int len = 1;
var buffer = new StringBuilder(len);
int result = UrlCreateFromPath(path, buffer, ref len, 0);
if(len == 1) Marshal.ThrowExceptionForHR(result);
buffer.EnsureCapacity(len);
result = UrlCreateFromPath(path, buffer, ref len, 0);
if(result == 1) throw new ArgumentException("Argument is not a valid path.", "path");
Marshal.ThrowExceptionForHR(result);
return new Uri(buffer.ToString());
}
[DllImport("shlwapi.dll", CharSet=CharSet.Auto, SetLastError=true)]
static extern int UrlCreateFromPath(string path, StringBuilder url, ref int urlLength, int reserved);
если путь начинается со специального префикса, он удаляется. Хотя в документации об этом не упоминается, функция выводит длину URL-адреса, даже если буфер меньше, поэтому я сначала получаю длину, а затем выделяю буфер.
некоторые очень интересные наблюдение у меня было, что, хотя "\\device\path" правильно преобразован в "file://device/path", в частности "\\localhost\path" преобразуется в "file:///path".
функция WinApi сумела кодировать специальные символы, но оставляет символы, специфичные для Юникода, не закодированными, в отличие от Uri construtor. В таком случае ...--8-->AbsoluteUri содержит правильно закодированный URL, в то время как OriginalString может использоваться для сохранения Юникода письмена.