Permalänk
Medlem

Läsa ÅÄÖ från fil (C/C++)

Följande kod ger en längd på 1 när jag skriver in ö:

wchar_t word[40]; wscanf(L"%ls", word); int len = wcslen(word); wprintf(L"Length: %d Word: %ls\n", len, word);

Men följande ger en längd på 2 med en fil som endast innehåller "ö" (eller 4 om den innehåller "öö"):

FILE* fPtr = fopen("line.txt", "r"); if (!fPtr) { printf("Input file cannot be opened\n"); return 1; } wchar_t word[40]; fwscanf(fPtr, L"%ls", word); //Eller fgetws(word, 40, fPtr); int len = wcslen(word); wprintf(L"Length: %d %ls\n", len, word); fclose(fPtr);

File contents:

ö

Output:

Length: 2 ├╢

Hur kan jag korrekt läsa svenska bokstäver från en fil till en array?

Windows 10 Pro 21H1
Visual Studio 2022 Community

Permalänk
Hedersmedlem

Hej!

Du stöter på problem med teckenkodning. Den vanligaste teckenkodningen idag är UTF-8, vilket är en väldigt flexibel teckenkodning som kan koda alla möjliga sorters tecken. Allt från svenska ÅÄÖ, till arabiska, kinesiska, emojis, och egyptiska fallossymboler till hieroglyfer.

Självklart kan inte allt detta få plats på bara 256 möjliga teckenkoder. Utan att gå in för mycket i detalj (läs gärna om UTF-8 och Unicode om du vill veta mer!) så innebär detta att ett ö, representerat i UTF-8, blir 2 byte långt, specifikt som 0xC3 0xB6. Mindre vanliga tecken (i alla fall från en västerländsk utgångspunkt) blir fler byte långa, medan normala A-Z, siffror, vanliga symboler, blir 1 byte långa, och därmed kompatibelt med ASCII.

Terminalen i Windows kör dock inte UTF-8 som standard, utan kör istället något som kallas för CP850. Det är en av flera möjliga "code pages", eller teckentabeller, som innehåller olika möjliga kombinationer för tecken. Dessa har 1 tecken per byte, men kan i gengäld inte representera alla möjliga tecken, bara de som finns i tabellen som är 256 tecken stor. Därför kan man behöva byta teckentabell beroende på vilket språk man vill skriva i, vilket är superjobbigt och varför man numera använder Unicode, som är superjobbigt fast på ett annat sätt.

Andra varianter som används i sverige är CP437, CP1252, Latin-1 och Latin-15. ÅÄÖ ligger inte ens på samma ställen på alla dessa.

För att ytterligare röra till det verkar du använda wprintf() och fwscanf(), där w:et innebär "wide character", vilket representerar alla tecken som 2 bytes (förutom de som inte får plats i 2 bytes, ja det är komplicerat), nämligen UTF-16, vilket Microsoft av någon anledning älskar, när resten av världen använder UTF-8.

Det finns inget lätt svar på frågan utan att ha bakgrundsinformation kring olika möjliga teckenkodningar.

Övergripande, du behöver ha koll på vilken kodning dina tecken är i din fil, läsa in dem på ett korrekt sätt (vilket inte nödvändigtvis blir 1 byte per tecken, eller ens ett tecken per bokstav om man använder combining characters...), och sedan ha koll på vilket format din terminal eller din utdata behöver vara i, och konvertera vid behov.

Till att börja med, vilken teckenkodning är din fil med "ö" sparad i?

Permalänk
Medlem

Jobba alltid utf-8, så försvinner 99% av felen

Permalänk
Medlem

Filen är UTF-8. Testade att använda widechar-versionerna eftersom det löste det för input från terminalen.

Finns det alltså ingen lösning i standardbiblioteket (för C) för att läsa UTF-8-filer som bokstäver istället för individuella bytes?

Permalänk
Medlem

Nu kan jag inte C, men du torde kunna läsa in filen som en stream, eller byte array, som du sen gör om till sträng, med utf-8 encoding

Permalänk
Medlem

Skrev en egen lösning som helt enkelt ignorerar den första byte:en (som är samma för å, ä och ö) för tecken som består utav flera, och sedan lägger till den igen när det ska skrivas ut. Kommer säkerligen att stöta på problem med andra icke-ASCI-tecken än å, ä och ö men det fungerar till det jag skulle ha det till.

FILE* fIn = fopen("in.txt", "r"); char word[100]; char byte; int c = 0; while ((byte = fgetc(fIn)) != EOF) { //Character is more than one byte if (byte < 0) { word[c] = fgetc(fIn); c++; } //Standard character, one byte. else { word[c] = byte; c++; } } word[c] = 0; fclose(fIn); FILE* fOut = fopen("out.txt", "w"); for (c = 0; word[c]; c++) { //å, ä, ö if (word[c] < 0) { // 0xC3 is the first byte for å, ä and ö fputc(0xC3, fOut); fputc(word[c], fOut); } //Standard character else { fputc(word[c], fOut); } } fclose(fOut); return 0;

Permalänk
Medlem
Skrivet av Ernesto:

Nu kan jag inte C, men du torde kunna läsa in filen som en stream, eller byte array, som du sen gör om till sträng, med utf-8 encoding

Problemet är att det inte direkt finns strängar i C. Närmaste är char arrays där char är 1 byte. Antar att man skulle kunna lagra unicode-tecken som en int (4 bytes) array om man vill göra det korrekt.

Permalänk
Medlem
Skrivet av TMG:

Filen är UTF-8. Testade att använda widechar-versionerna eftersom det löste det för input från terminalen.

Finns det alltså ingen lösning i standardbiblioteket (för C) för att läsa UTF-8-filer som bokstäver istället för individuella bytes?

Det finns. Typ. Sort of. Nästan, fast omständligt.
Vad som finns är dels stöd för multibyte characters och stöd för wide characters.
Dessutom så kan man sätta locale med setlocale() så att biblioteket vet vilka regler som skall användas.

Så, om din C implentation har en locale med UTF8 stöd, så skall det gå att hantera, med en massa krånglig kod för att konvertera fram och tillbaka.

Rekommenderas att du hellre, om du har möjlighet, att använda något bibliotek som har direkt stöd för utf-8 - det blir antagligen mycket enklare på det sättet.