odczyt pliku utf-8 za pomoca biblioteki standardowej

0

Jak plik jest zapisany jako UTF-8 to czy można go odczytywać std::fstreamem? Czy trzeba użyć jakiejs biblioteki niestandardowej?
Ja odczytałem go do stringa i nie mam wtedy np. polskich znaków. Ale jak masz np. katalog z plikami i robisz:

 for (const auto& entry : fs::directory_iterator(inputDir))
    {
        s filepath = entry.path().string();
    }

to string filepath pokazuje Ci polskie znaki. Więc skoro dla odczytu plików w filesystemie istnieje łatwy sposób, to myślę, że musi istnieć w standardzie C++ też sposób odczytu pliku jakiś taki prosty aby w stringu czy stringstreamie były te polskie znaki.

2

i szukałeś i jak zwykle nigdzie nie było bo to przecież jest tak niewiarygodne, żeby ktoś przed tobą próbował wczytać w c++ text w unicode...
https://www.google.com/search?q=c%2B%2B+read+unicode+file

0

jest dużo tego ale to są jakieś takie niewiarygodnie dziwne rozwiązania z ifdefami na WIN32. Jakoś nie mogę w to uwierzyć, żeby nie było łatwego sposobu do odczytu pliku utf-8 skoro istnieje prosty sposób do skanu filesystemu i pobrania nazw plików z polskimi/niemieckiemi znakami.

0

Najpierw zastanow sie czy na pewno rozumiesz co chcesz zrobic. Bo plik zapisany w UTF-8 to jest po prostu tablica byte, wiec odpowiedz na pytanie brzmi: oczywiscie ze da sie odczytac za pomoca std. Przyklad:

$ xxd utf8.txt
00000000: efbb bfc5 82c4 85c5 bac4 87c5 bcc3 b3c4  ................
00000010: 990a                                     ..
$ cat utf8.cpp
#include <iostream>
#include <fstream>
#include <string>
int main() {
  std::ifstream stream("utf8.txt", std::ios::binary);
  std::string str;
  stream >> str;
  std::cout << str << std::endl;
  return 0;
}
$ g++ -Wall -Werror -pedantic utf8.cpp && ./a.out 
łąźćżóę

Nawet BOM zostal poprawnie pominiety (co mnie nieco zdziwilo).

0

Czytam plik używając klasy fstream. Tak jest najlepiej bo jak API do wstringa sie użyje to i tak nie ma narodowych znaków.
Więc jak mam kontent pliku utf-8 w postaci stringa to teraz potrzebuje metody do konwersji string do wstringa. Przed C++17 była taka:

return std::wstring_convert<std::codecvt_utf8<wchar_t>>().from_bytes(str);

ale ona jest deprecated i teraz trzeba includowac nagłówek Windows.h i użyć tego:

std::wstring convertStringToWstring(const std::string& str)
{
	size_t count = MultiByteToWideChar(CP_ACP, 0, str.c_str(), str.length(), NULL, 0);
	std::wstring wstr(count, 0);
	MultiByteToWideChar(CP_ACP, 0, str.c_str(), str.length(), &wstr[0], count);
	return wstr;
}

i to sie kompiluje ale nie zamienia znaków ASCII na UTF-8. I z tym jest problem.

0

bo jak się użyje tego do odczytu pliku:

    std::wifstream file(L"sciezka");
    std::wstringstream ws;
    ws << file.rdbuf();

to i tak w streamie ws nie ma narodowych znaków.

0
eleventeen napisał(a):
$ xxd utf8.txt
00000000: efbb bfc5 82c4 85c5 bac4 87c5 bcc3 b3c4  ................
00000010: 990a                                     ..
$ cat utf8.cpp
#include <iostream>
#include <fstream>
#include <string>
int main() {
  std::ifstream stream("utf8.txt", std::ios::binary);
  std::string str;
  stream >> str;
  std::cout << str << std::endl;
  return 0;
}
$ g++ -Wall -Werror -pedantic utf8.cpp && ./a.out 
łąźćżóę

ok to jeszcze spróbuję jako binarny odczytać do streama i zobacze co z tego wyjdzie a nie iść w rozwiązanie z wstringiem

0

Mi 3 dni zajęło, żeby zaimplementować obsługę UTF-8 do własnego edytora, zapisywał odczytywał polskie znaki. Bez żadnych bibliotek.

0

Można podejść tak: Każdy plik jest ciągiem bajtowym, więc można odczytać całą zawartość do listy lub tablicy char, a potem zamienić na tekst już jakimś algorytmem.

Ja sam miałem dość podobny problem, czyli odczytywanie i zapisywanie tekstu w różnych kodowaniach i podam takiego gotowca:
https://github.com/andrzejlisek/TextPaintWeb/blob/main/prog/textcodec.h
https://github.com/andrzejlisek/TextPaintWeb/blob/main/prog/textcodec.cpp

Przykład użycia obiektu typu TextCodec:

// Zapis pliku:
Codec.get()->Reset();
Codec.get()->AddBOM();
Codec.get()->EnqueueStr(Text);
Codec.get()->DequeueRaw(BinaryData);

// Odczyt pliku:
Codec.get()->Reset();
Codec.get()->EnqueueRaw(BinaryData);
Codec.get()->RemoveBOM();
Codec.get()->DequeueStr(Text);

Idea jest taka, że obiekt konstruuje się numerem kodeka, numery są takie same, jak TextEncoding w C#. Sam kodek działa tak, że w każdej chwili można dopisać dane jako string EnqueueStr, wewnątrz zamienią się na dane surowe i można wyciągnąć jako dane surowe EnqueueRaw. Implementację zamiana danych surowych na tekstowe jest w funkcji DequeueStr. Przy tej implementacji można konwertować tekst fragmentami, nie potrzeba wczytywać od razu całego pliku do kodeka i z niego wyciągać. Kodek też pamięta stan konwersji, bo np. w UTF-8 jeden znak może zajmować 2 lub 3 bajty.

Inaczej mówiąc, można powiedzieć, że kodek działa na zasadzie strumieni, że wchodzi strumień bajtowy i wychodzi strumień tekstowy lub wchodzi strumień tekstowy i wychodzi strumień bajtowy.

Możesz przejrzeć cały projekt, nie widze sensu wstawiać implementacji typów Raw i Str, są to odpowiednio tablica danych surowych i odpowiednik zwykłego String. Własne struktury w tym przypadku wynikają z tego, że ja ten program przerabiałem z C# i chciałem mieć interfejs standardowych struktur podobny do tego w C#, a potem dopisywałem dodatkowe funkcje w miare potrzeb.

Myślę, że bez większego trudu można przerobić funkcje kodek tak, żeby wchodziły do nich standardowe struktury, jak np std::vector do danych surowych i std::string do tekstu.

Jest jeszcze inny problem: Typ std::string przeznacza jeden bajt na tekst i w zależności od kompilatora może domyślnie interpretować jako UTF-8 przy cin i cout, jestdnak trzeba to mieć na uwadze. Jest to jeszcze jeden powód, dla którego utworzyłem customowy typ Str, który tak naprawdę jest wektorem liczb i nie ma problemu z dziwnymi znakami. Pierwotnie, w C# też posługiwałem się listą liczb, bo w tym przypadku były jakieś kłopoty przy "egzotycznych" znakach.

0
fvg napisał(a):

ale ona jest deprecated i teraz trzeba includowac nagłówek Windows.h i użyć tego:

std::wstring convertStringToWstring(const std::string& str)
{
	size_t count = MultiByteToWideChar(CP_ACP, 0, str.c_str(), str.length(), NULL, 0);
	std::wstring wstr(count, 0);
	MultiByteToWideChar(CP_ACP, 0, str.c_str(), str.length(), &wstr[0], count);
	return wstr;
}

i to sie kompiluje ale nie zamienia znaków ASCII na UTF-8. I z tym jest problem.

CP_ACP przypuszczalnie nie jest ustawione na UTF-8. Jeśli wiesz, że konwertowany tekst jest w UTF-8, to lepiej używać CP_UTF8.

Innym podejściem może być wywołanie imbue na strumieniu.

my_stream.imbue(locale(".UTF-8"));

0
-daniel- napisał(a):
fvg napisał(a):

ale ona jest deprecated i teraz trzeba includowac nagłówek Windows.h i użyć tego:

std::wstring convertStringToWstring(const std::string& str)
{
	size_t count = MultiByteToWideChar(CP_ACP, 0, str.c_str(), str.length(), NULL, 0);
	std::wstring wstr(count, 0);
	MultiByteToWideChar(CP_ACP, 0, str.c_str(), str.length(), &wstr[0], count);
	return wstr;
}

i to sie kompiluje ale nie zamienia znaków ASCII na UTF-8. I z tym jest problem.

CP_ACP przypuszczalnie nie jest ustawione na UTF-8. Jeśli wiesz, że konwertowany tekst jest w UTF-8, to lepiej używać CP_UTF8.

Innym podejściem może być wywołanie imbue na strumieniu.

my_stream.imbue(locale(".UTF-8"));

Pomogło, czyli funkcja do konwersji znaków ASCI na UTF-8 wstring to:

#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
std::wstring convertStringToWstring(const std::string& str)
{
	size_t count = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), str.length(), NULL, 0);
	std::wstring wstr(count, 0);
	MultiByteToWideChar(CP_UTF8, 0, str.c_str(), str.length(), &wstr[0], count);
	return wstr;
}
0
fvg napisał(a):

Jak plik jest zapisany jako UTF-8 to czy można go odczytywać std::fstreamem? Czy trzeba użyć jakiejs biblioteki niestandardowej?
Ja odczytałem go do stringa i nie mam wtedy np. polskich znaków. Ale jak masz np. katalog z plikami i robisz:

Da się ale jest to popolupo.

  1. trzeba ustawić global locale na takie jakie ma użyać twoja aplikacja (nie musi być to UTF-8).
  2. trzeba użyć locale z UTF-8 i ustawić je na strumieniu przez imbue
  3. trzeba pamiętać, że nazwy locale nie są dobrze ustandaryzowane. Np .UTF-8 zadziała tylko na Windows, C.UTF-8 tylko na Linux en_US.UTF-8 zadziała tam, gdzie amerykański model językowy jest zainstalowany (teoretycznie wszędzie, ale mi się kiedyś trafiła niespodzianka).
  4. Metoda nie działa na standardowym strumieniu wejściowym (std::cin), jeszcze nie wiem czemu.
#ifdef IS_WINDOWS
    static constexpr char Utf8LocaleName[] = ".UTF-8";
#elif IS_DARWIN
    static constexpr char Utf8LocaleName[] = "en_US.UTF-8";
#else
    static constexpr char Utf8LocaleName[] = "C.UTF-8";
#endif

void setupGlobalLocale()
try
{
    // inform C API that application logic uses UTF-8 encoding
    if (!setlocale(LC_CTYPE, Utf8LocaleName))
    {
        perror("set C locale");
    }

    auto utf8Locale = std::locale{Utf8LocaleName};
    // inform C++ API that application logic uses UTF-8 encoding
    std::locale::global(utf8Locale);
}
catch (const std::exception& e)
{
    std::cerr << "Failed to configure global locale: " << e.what() << std::endl;
}

void processFile(std::filesystem::path& filePath)
{
    std::istream in{filePath};
    in.exceptions(std::ios::badbit);
    in.imbue(std::locale{Utf8LocaleName});
    processStream(in);
}

Jakieś moje wątki na SO, związane z obsługą kodowania znaków:

Jakieś moje repo z eksperymentem, który działa na C++ i crashuje na wersji C: https://github.com/MarekR22/mixEncodingsInC

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