Czy konwertować ścieżkę pliku na identyfikator URI pliku?

201

Czy .NET Framework ma jakieś metody konwersji ścieżki (np. "C:\whatever.txt") Na identyfikator URI pliku (np. "file:///C:/whatever.txt")?

System.Uri klasa ma odwróconych (z pliku URI do ścieżki bezwzględnej), ale nie tak daleko, jak mogę znaleźć do konwersji do pliku URI.

Ponadto nie jest to aplikacja ASP.NET.

Tinister
źródło

Odpowiedzi:

291

System.UriKonstruktor ma zdolność do analizowania pełnych ścieżek plików i przekształcić je w stylu ścieżki URI. Możesz więc po prostu wykonać następujące czynności:

var uri = new System.Uri("c:\\foo");
var converted = uri.AbsoluteUri;
JaredPar
źródło
78
var path = new Uri("file:///C:/whatever.txt").LocalPath;zamienia Uri z powrotem w lokalną ścieżkę plików dla każdego, kto tego potrzebuje.
Pondidum
2
Jako uwaga Tego rodzaju Uri można kliknąć w danych wyjściowych VS i
wynikach
7
To niestety nie jest poprawne. Na przykład new Uri(@"C:\%51.txt").AbsoluteUridaje "file:///C:/Q.txt"zamiast"file:///C:/%2551.txt"
poizan42
2
nie będzie to działać ze ścieżką ze spacjami, tj .: „C: \ test folder \
okolwiek.txt
Nie będzie to również działać ze ścieżkami zawierającymi znak #.
lewis
42

Nikt nie zdaje sobie sprawy z tego, że żaden z System.Urikonstruktorów nie obsługuje poprawnie niektórych ścieżek ze znakami procentowymi.

new Uri(@"C:\%51.txt").AbsoluteUri;

To daje "file:///C:/Q.txt"zamiast "file:///C:/%2551.txt".

Żadna z wartości przestarzałego argumentu dontEscape nie robi żadnej różnicy, a określenie UriKind daje również ten sam wynik. Próbowanie z UriBuilder nie pomaga:

new UriBuilder() { Scheme = Uri.UriSchemeFile, Host = "", Path = @"C:\%51.txt" }.Uri.AbsoluteUri

To również powraca "file:///C:/Q.txt".

O ile mogę stwierdzić, frameworkowi brakuje właściwie żadnego sposobu, aby to zrobić poprawnie.

Możemy tego spróbować, zastępując ukośniki odwrotne ukośnikami i kierując ścieżkę do Uri.EscapeUriString- tj

new Uri(Uri.EscapeUriString(filePath.Replace(Path.DirectorySeparatorChar, '/'))).AbsoluteUri

Z początku wydaje się, że to działa, ale jeśli podasz ścieżkę, C:\a b.txtto file:///C:/a%2520b.txtzamiast tego file:///C:/a%20b.txt- w jakiś sposób decyduje, że niektóre sekwencje powinny zostać zdekodowane, a inne nie. Teraz możemy po prostu przedrostek ze "file:///"sobą, jednak nie bierze to \\remote\share\foo.txtpod uwagę ścieżek UNC, jak się wydaje - ogólnie wydaje się, że w systemie Windows powszechnie przyjmuje się przekształcanie ich w pseudo-adresy URL formularza file://remote/share/foo.txt, dlatego też powinniśmy to wziąć pod uwagę.

EscapeUriStringma również problem polegający na tym, że nie ucieka '#'postaci. Wydaje się w tym momencie, że nie mamy innego wyjścia, jak stworzyć własną metodę od zera. Oto co sugeruję:

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();
}

To celowo pozostawia + i: niezakodowane, ponieważ wydaje się, że tak zwykle dzieje się w systemie Windows. Koduje także Latin1, ponieważ Internet Explorer nie może zrozumieć znaków Unicode w adresach URL plików, jeśli są one zakodowane.

poizan42
źródło
Czy istnieje model użytkowy, który obejmuje to licencją liberalną? Szkoda, że ​​nie ma właściwego sposobu na to, aby
istniało
4
Możesz użyć powyższego kodu zgodnie z warunkami licencji MIT (nie uważam, że coś tak krótkiego powinno nawet podlegać prawom autorskim, ale teraz masz wyraźną zgodę)
poizan42
9

Powyższe rozwiązania nie działają w systemie Linux.

Przy użyciu .NET Core próba wykonania new Uri("/home/foo/README.md")powoduje wyjątek:

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)
   ...

Musisz dać CLR kilka wskazówek na temat tego, jaki masz adres URL.

To działa:

Uri fileUri = new Uri(new Uri("file://"), "home/foo/README.md");

... a ciąg zwracany przez fileUri.ToString()to"file:///home/foo/README.md"

Działa to również w systemie Windows.

new Uri(new Uri("file://"), @"C:\Users\foo\README.md").ToString()

... emituje "file:///C:/Users/foo/README.md"

Bob Stine
źródło
Jeśli wiesz, że ścieżka jest absolutna, możesz użyćnew Uri("/path/to/file", UriKind.Absolute);
Minijack
8

VB.NET:

Dim URI As New Uri("D:\Development\~AppFolder\Att\1.gif")

Różne wyjścia:

URI.AbsolutePath   ->  D:/Development/~AppFolder/Att/1.gif  
URI.AbsoluteUri    ->  file:///D:/Development/~AppFolder/Att/1.gif  
URI.OriginalString ->  D:\Development\~AppFolder\Att\1.gif  
URI.ToString       ->  file:///D:/Development/~AppFolder/Att/1.gif  
URI.LocalPath      ->  D:\Development\~AppFolder\Att\1.gif

Jedna wkładka:

New Uri("D:\Development\~AppFolder\Att\1.gif").AbsoluteUri

Wyjście :file:///D:/Development/~AppFolder/Att/1.gif

MrCalvin
źródło
2
AbsoluteUrijest poprawny, ponieważ koduje również spacje do% 20.
psulek
Jestem przekonany, że cierpią na to te same problemy, które opisano w odpowiedzi dotyczącej obsługi znaków specjalnych .
binki
4

Przynajmniej w .NET 4.5+ możesz także:

var uri = new System.Uri("C:\\foo", UriKind.Absolute);
Gavin Greenwalt
źródło
1
Czy nie ryzykujesz UriFormatExceptionjednego dnia?
berezovskyi
To też nie działa poprawnie, new Uri(@"C:\%51.txt",UriKind.Absolute).AbsoluteUrizwraca "file:///C:/Q.txt"zamiast"file:///C:/%2551.txt"
poizan42
2

UrlCreateFromPath na ratunek! Cóż, nie do końca, ponieważ nie obsługuje formatów rozszerzonych i ścieżek UNC, ale nie jest to trudne do pokonania:

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);

W przypadku, gdy ścieżka zaczyna się od specjalnego prefiksu, zostaje usunięta. Chociaż dokumentacja nie wspomina o tym, funkcja podaje długość adresu URL, nawet jeśli bufor jest mniejszy, więc najpierw uzyskuję długość, a następnie przydzielam bufor.

Kilka bardzo interesujących spostrzeżeń, jakie miałem, to że „\\ urządzenie \ ścieżka” jest poprawnie przekształcana na „plik: // urządzenie / ścieżka”, a konkretnie „\\ localhost \ ścieżka” jest przekształcana na „plik: /// ścieżka” .

Funkcja WinApi zdołała zakodować znaki specjalne, ale pozostawia znaki specyficzne dla Unicode niezakodowane, w przeciwieństwie do konstruktora Uri . W takim przypadku AbsoluteUri zawiera poprawnie zakodowany adres URL, a OriginalString może być używany do zachowania znaków Unicode.

IllidanS4 chce powrotu Moniki
źródło
0

Obejście jest proste. Wystarczy użyć metody Uri (). ToString () i później kodować białe spacje, jeśli takie istnieją.

string path = new Uri("C:\my exampleㄓ.txt").ToString().Replace(" ", "%20");

poprawnie zwraca plik: /// C: / my% 20example ㄓ .txt

Rzeczy się zdarzają
źródło