Porównywanie absolutnych i relatywnych ścieżek do plików z wykorzystaniem UTF-8

0

Pracuję nad kawałkiem kodu, który sprawdza czy na liście znajduje się zadana ścieżka do pliku dyskowego; Metoda pobiera na wejściu dowolną ścieżkę, względną lub bezwzględną i ma ją porównać z listą ścieżek, względnych lub bezwzględnych; Całość musi opierać się o UTF-8 (to konieczne) oraz musi być niezależna od platformy;

I teraz tak - każdą ścieżkę przed porównaniem przepuszczam przez metodę ExpandFileNameUTF8, aby przerobić je na ścieżki absolutne, bo tylko wtedy jest sens je porównywać;


Jak teraz porównać te dwie ścieżki?

Mogę użyć funkcji SameFileName, jednak używa ona funkcji AnsiCompareFileName i za bardzo nie nadaje się do UTF-8; Z kolei ona sprawdza stan stałej FileNameCaseSensitive i na jej podstawie wywołuje albo funkcję AnsiCompareStr, albo AnsiCompareText; Kod wyglądałby tak:

if SameFileName(strFullInputFileName, strFullLinkedFileName) then
  Exit(False);

Kolejna rzecz to istnienie specjalnych funkcji porównujących łańcuchy kodowane w UTF-8 - UTF8CompareStr i UTF8CompareText; Skoro one przeznaczone są dla wymaganego kodowania to można by ich użyć w tym przypadku;

Mogę posłużyć się wymienioną stałą i na jej podstawie wywołać jedną z dwóch metod porównujących:

if FileNameCaseSensitive then
  Result := UTF8CompareStr(strFullInputFileName, strFullLinkedFileName) = 0
else
  Result := UTF8CompareText(strFullInputFileName, strFullLinkedFileName) = 0;

if Result then
  Exit(False);

a mogę też skorzystać z dyrektyw kompilatora i wkompilować jedynie wymaganą funkcję, np.:

{$IFDEF WINDOWS}
if UTF8CompareText(strFullInputFileName, strFullLinkedFileName) = 0 then
  Exit(False);
{$ELSE}
if UTF8CompareStr(strFullInputFileName, strFullLinkedFileName) = 0 then
  Exit(False);
{$ENDIF}

albo coś na ten kształt; Tyle że to ostatnie rozwiązanie ma ograniczenia - jakoś ten hardkodowany WINDOWS nie wygląda dobrze, a i nie wiem czy jest jeszcze jakiś system, który tak samo jak Windows nie dba o wielkość znaków;

W jaki sposób poprawnie porównywać relatywne i absolutne ścieżki, aby porównanie współgrało z UTF-8 oraz było niezależne od systemu operacyjnego? Będę wdzięczny za jakieś porady;

W razie czego obecny kod metody wygląda tak:

function TTSInfoLinkedTreesList.LinkedFileNotYetBeenProcessed(const AFileName: UTF8String): Boolean;
var
  pliLink: PListItem;
  strFullInputFileName, strFullLinkedFileName: UTF8String;
begin
  if FCount > 0 then
  begin
    strFullInputFileName := ExpandFileNameUTF8(AFileName);
    pliLink := FFirstItem;

    while pliLink <> nil do
    begin
      strFullLinkedFileName := ExpandFileNameUTF8((pliLink^.Element as TTSInfoLink).FileName);

      if SameFileName(strFullInputFileName, strFullLinkedFileName) then
        Exit(False)
      else
        pliLink := pliLink^.NextItem;
    end;
  end;

  Result := True;
end;

PS: Jest jeszcze funkcja ExpandFileNameCase w razie czego...
PPS: Funckja SameFileName w rezultacie korzysta z metody z menedżera stringów, więc może i nada się..?

1

@furious programming ja bym jednak użył tego $IFDEF WINDOWS Jeśli Ci zależy na przenośności to chyba będzie jedyne dobre rozwiązanie. Bo jak sam widzisz są problemy ponieważ systemy operacyjnie różnie się zachowują jeśli chodzi o wielkość liter w nazwach plików. Bo ja innego sposobu nie znam. Ogólnie podczas pisania na rożne platformy przecież bardzo często spotyka się takie dyrektywy. Jak dla mnie to nic złego.

0

Czyli jednak w ten sposób kombinować; Zależy mi głównie na systemach Windows i systemach uniksowych;

Ja wiem, że takie dyrektywy to nic złego - w samych źródłach biblioteki standardowej jest ich masa; O ile kod całej mojej biblioteki nie jest uzależniony od platformy, to niestety ale pewne zabezpieczenie muszę wprowadzić, które może działać prawidłowo jedynie na podstawie porównywania ścieżek, a to różnie wygląda na wymieniowych systemach;

No nic, w takim razie wypróbuję te dyrektywy; Ale nie mam pod ręką jakiegokolwiek komputera z systemem uniksowym, więc nie będę miał jak tego sprawdzić (pod Windows wszystko gra).

0

Zastanawiam się tylko czy ktoś naprawdę tworzy katalogi czy nazwy plików różniące się tylko wielkością liter. Bo jeśli to nie jest to takie częste to można by było się pokusić o sprawdzanie po zmianie liter na wielkie. Jednak takie rozwiązanie to świadome wprowadzanie błędu. Pytanie tylko jak często będzie się ujawniać.

0

@Mr.YaHooo - nie wiem czy często jest to stosowane; W językach programowania, w których wielkość liter jest rozróżniana, używa się identyfikatorów np. z wielkiej i małej litery, a ich znaczenie jest zupełnie różne (np. nazwa klasy i nazwa obiektu); Skoro więc taka możliwość istnieje i ludzie z niej korzystają to podobnie będzie z nazwami plików;

Nie mogę na sztywno przyjąć, że nikt nie będzie używał takich samych znaków w nazwach plików czy katalogów, choć zezwala na to system; Wprowadziłbym celowo ograniczenie, które uniemożliwi prawidłową pracę kodu;

Równie dobrze mógłbym na sztywno wpisać tam funkcję UTF8CompareStr i zawsze rozróżniać wielkość liter, ale pod Windows wielkość znaków jest obojętna, więc to także było by ograniczenie; A Windows nadal jest najpopularniejszym systemem na desktopach, więc zrobiłbym sobie kuku;

Dam znać jak coś wykombinuję - muszę dokładnie przeglądnąć bebechy RTL; Może znajdę tam odpowiedź.

0

Tak, to prawda. Takie celowe ograniczanie jest raczej nie do przyjęcia. Zatem ja nic innego nie jestem w stanie Ci zaproponować. Chyba, ze uda Ci się znaleźć coś we wnętrzu biblioteki.

0

Chyba, ze uda Ci się znaleźć coś we wnętrzu biblioteki.

Zobaczę po prostu co po kolei jest wykonywane i jak to dalej działa, jeśli użyję funkcji SameFileName; Zapewne dojdę do jakiegoś wywołania z menedżera stringów, ale debugger też coś może dopomóc; Ewentualnie rozglądnę się za jakimiś kodami, w których takie rzeczy występują.

0

No nic - finalnie skorzystam z dyrektyw kompilatora do odróżnienia systemu;

Niestety stała FileNameCaseSensitive wbita jest do kodu na sztywno - nie jest objęta w jakieś dyrektywy i zawsze posiada wartość True, nawet pod Windows; Trochę mnie to dziwi:

Description napisał(a)

FileNameCaseSensitive is True if case is important when using filenames on the current OS. In this case, the OS will treat files with different cased names as different files. Note that this may depend on the filesystem: Unix operating systems that access a DOS or Windows partition will have this constant set to true, but when writing to the DOS partition, the casing is ignored.

This constant is part of a set of constants that describe the OS characteristics. These constants should be used instead of hardcoding OS characteristics.

Póki co takie rozwiązanie pozostanie; Szkoda, że w module LazUTF8 nie ma odpowiednika dla UTF-8, np. UTF8SameFileName lub SameFileNameUTF8; W każdym razie trzeba to ręcznie obejść;

W razie czego finalny kod metody wygląda tak:

function TTSInfoLinkedTreesList.LinkedFileNotYetBeenProcessed(const AFileName: UTF8String): Boolean;
var
  pliLink: PListItem;
  strFullInputFileName, strFullLinkedFileName: UTF8String;
begin
  if FCount > 0 then
  begin
    strFullInputFileName := ExpandFileNameUTF8(AFileName);
    pliLink := FFirstItem;

    while pliLink <> nil do
    begin
      strFullLinkedFileName := ExpandFileNameUTF8((pliLink^.Element as TTSInfoLink).FileName);

      {$IFDEF WINDOWS}
      if UTF8CompareText(strFullInputFileName, strFullLinkedFileName) = 0 then
      {$ELSE}
      if UTF8CompareStr(strFullInputFileName, strFullLinkedFileName) = 0 then
      {$ENDIF}
        Exit(False)
      else
        pliLink := pliLink^.NextItem;
    end;
  end;

  Result := True;
end;
0

@furious programming czyli z tego wynika, że to co biblioteka posiada w standardzie też jest nie do końca ok? Bo Twoje rozwiązanie jest z tego co mi się wydaje najlepsze.

0

Tego nie jestem pewien, bo nie do końca wiem dlaczego stała FileNameCaseSensitive nie jest objęta w dyrektywy kompilatora, przez co zawsze posiada wartość True; Nie wiem też ile wspólnego ma instalacja środowiska do zawartości modułów z biblioteki standardowej;

Moje rozwiązanie i tak posiada jedną wadę, która może (ale nie musi) przekłamać porównanie; Wynika to z tego:

Note that this may depend on the filesystem: Unix operating systems that access a DOS or Windows partition will have this constant set to true, but when writing to the DOS partition, the casing is ignored.

Ale i tak obecne rozwiązanie mnie satysfakcjonuje, więc póki co nie będę nic zmieniać, bo cytat opisuje zapewne sytuację niecodzienną, niepospolitą i bardzo rzadką; Trudno tu więc o jakieś krótkie, szybkie i sensowne rozwiązanie.

0

Niestety ja też tego nie wiem. Co więcej jeszcze ani razu nie musiałem porównywać ścieżek. Jedyne co porównywałem, to nazwy plików.

0

No i jak to robiłeś?

O to właśnie chodzi w tym wątku, że problemem nie jest relatywna czy absolutna ścieżka, a wybranie odpowiedniej metody porównawczej dla danej platformy; Całą robotę z rozwijaniem ścieżek i zmianą separatorów odwala funkcja ExpandFileName, więc pozostaje jedynie wybranie funkcji; A skoro już mowa o relatywnych ścieżkach dostępu to oczywiście możliwe jest podanie samych nazw plików.

0

Jako, że byłem ograniczony tylko do programów działających pod kontrolą Windows użyłem zwykłego porównania stringów po zamianie liter na wielkie i już. Przy okazji odkryłem, że w C++ Builderze funkcja ExtractFileName ma błąd i źle parsuje ścieżki Linuxowe gdzie ukośniki są odwrotnie do tych w Windows.

1 użytkowników online, w tym zalogowanych: 0, gości: 1