2014-11-14 22 views
8

Muszę odczytywać znaki UTF-8 z pliku tekstowego i przetwarzać je. na przykład w celu obliczenia częstotliwości występowania określonego znaku. Zwykłe znaki są w porządku. Problem występuje w przypadku znaków takich jak ü lub ğ. Oto mój kod, aby sprawdzić, czy dany znak występuje porównanie kodu ASCII znaku przychodzącej:Przetwarzaj znaki UTF-8 w C z pliku tekstowego

FILE * fin; 
FILE * fout; 
wchar_t c; 
fin=fopen ("input.txt","r"); 
fout=fopen("out.txt","w"); 
int frequency = 0; 
while((c=fgetwc(fin))!=WEOF) 
{ 
    if(c == SOME_NUMBER){ frequency++; } 
} 

SOME_NUMBER jest to, czego nie może dowiedzieć się o tych znaków. W rzeczywistości te znaki drukują 5 różnych liczb, próbując wydrukować je w postaci dziesiętnej. podczas gdy na przykład dla postaci 'a' zrobiłbym jako: if(c == 97){ frequency++; }, ponieważ kod ascii 'a' to 97. Czy w każdym razie mogę zidentyfikować te znaki specjalne w C?

P.S. Praca ze zwykłym char (nie wchar_t) tworzy ten sam problem, ale tym razem drukowanie dziesiętnego odpowiednika przychodzącej litery wydrukowałoby 5 różnych NEGATYWNYCH liczb dla tych znaków specjalnych. Problem stoi.

+0

Większość postaci zajmuje więcej niż jeden bajt. Dlatego powinieneś przeczytać i porównać wiele bajtów. Jeśli chodzi o 'wchar_t', to myślę, że jest to zdefiniowane przez implementację, jakie funkcje kodowania znaków działają jak' fgetwc', a na wielu systemach nie jest to UTF-8. – delnan

+0

W zależności od użytej czcionki, kilka punktów kodowych może mieć identyczne lub prawie identyczne glify. Wciąż są różne punkty kodowe w Unicode. Jakie jest widmo wejściowe i jakie widmo (kodowanie) ma zostać zamodelowane? –

+0

@didierc jak utworzyć taką tabelę? czy mógłbyś dać mi kilka wskazówek na ten temat w odpowiedzi? co powinienem przypisać do tych znaków specjalnych w moim stole? – Ams

Odpowiedz

4

Można utworzyć własną funkcję odczytu dekodowania utf-8.

patrz opis formatu, w https://en.wikipedia.org/wiki/UTF-8

ten kod nie jest bardzo ładne i solidne. Ale to jest szkic tego, co ment ...

#include <stdio.h> 
#include <stdlib.h> 

#define INVALID (-2) 

int fgetutf8c(FILE* f) 
{ 
    int result = 0; 
    int input[6] = {}; 

    input[0] = fgetc(f); 
    printf("(i[0] = %d) ", input[0]); 
    if (input[0] == EOF) 
    { 
     // The EOF was hit by the first character. 
     result = EOF; 
    } 
    else if (input[0] < 0x80) 
    { 
     // the first character is the only 7 bit sequence... 
     result = input[0]; 
    } 
    else if ((input[0] & 0xC0) == 0x80) 
    { 
     // This is not the beginning of the multibyte sequence. 
     return INVALID; 
    } 
    else if ((input[0] & 0xfe) == 0xfe) 
    { 
     // This is not a valid UTF-8 stream. 
     return INVALID; 
    } 
    else 
    { 
     int sequence_length; 
     for(sequence_length = 1; input[0] & (0x80 >> sequence_length); ++sequence_length); 
     result = input[0] & ((1 << sequence_length) - 1); 
     printf("squence length = %d ", sequence_length); 
     int index; 
     for(index = 1; index < sequence_length; ++index) 
     { 
      input[index] = fgetc(f); 
      printf("(i[%d] = %d) ", index, input[index]); 
      if (input[index] == EOF) 
      { 
       return EOF; 
      } 
      result = (result << 6) | (input[index] & 0x30); 
     } 
    } 
    return result; 
} 

main(int argc, char **argv) 
{ 
    printf("open(%s) ", argv[1]); 
    FILE *f = fopen(argv[1], "r"); 
    int c = 0; 
    while (c != EOF) 
    { 
     c = fgetutf8c(f); 
     printf("* %d\n", c); 
    } 
    fclose(f); 
} 
+0

Czy mógłbyś podać jeszcze kilka porad na ten temat? być może, jak zacząć czytać z pliku i rozpoznawać znaki. – Ams

+1

Nie, z pewnością nie należy tego robić, twoja biblioteka C jest tam dla ciebie, nie wymyślaj ponownie koła. –

+0

Tak.Znajdźmy bibliotekę utf-8 lub system obsługujący utf-8 ... –

1

Jest to propozycja na rozwiązanie, które nie wymaga szerokich znaków:

Z Wikipedii: projekt UTF-8 wielo-bajtów sekwencji

się prowadzeniu „1” o 1 bajt liczbę następujących bajtów „10” na początku bajtu sygnały bajt kontynuacji „0” jako 1-cie bajt sygnalizuje sekwencję jednobajtowych

Byte 1 Byte 2 Byte 3 Byte 4 Byte 5 Byte 6
0xxxxxxx
110xxxxx 10xxxxxx
1110xxxx 10xxxxxx 10xxxxxx
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx

Dlatego trzeba najpierw wiedzieć, jeśli są umieszczone w kolejności wielo-bajtowego poprzez badanie:

char byte; 
// ... 
if((byte & 0xC0) == 0x80) 
{ 
    // Handle multi-byte 
} 

potem trzeba gromadzić bajt aż sekwencja jest zakończona (liczyć prowadząc 1 aby wiedzieć, ile potrzebujesz iteracji), a na końcu otrzymasz unikalny znak Unicode i możesz powiązać częstotliwość.

Interfejs API string.h działa dobrze z wielobajtową sekwencją UTF-8. Na przykład, można znaleźć wystąpienia ü (0xC3 0xBC) w ciąg str:

char sequence[] = {0xC3, 0xBC}; 
size_t count = 0 
for(;*str*;str++) 
{ 
    str = strstr(str,sequence); 
    if(str) 
    { 
     count++; 
    } 
} 
+1

'111110xx ...' i '1111110x ...' nie są już częścią UTF-8. Zobacz https://en.wikipedia.org/wiki/UTF-8#Description – chux

2

Jeśli trzeba to szerokie literały znakowe w kodzie, może to zrobić za pomocą następującego oznaczenia:

whar_t c = L'\u0041'; // 'A' 

Ale uważam, że nie powinieneś tego potrzebować, jeśli chcesz zachować statystyki częstotliwości postaci.Typ wchar_t pozwala łatwo porównać wartości jak inne integralne typy:

wchar_t c1 = L'\u0041', c2 = L'\u0030'; 
int r = c1 == c2; // 0 

Z tego operatora porównania i funkcji, aby wyodrębnić wchar_t od strumienia danych, powinieneś być w stanie zbudować asocjacyjną tabeli z wchar_t do unsigned int za pomocą swojego tylko znaki wejściowe (wiele implementacji hashtable C w Internecie).

Może Ważną kwestią jest to, że szerokość chars i utf8 znaki są różne rodzaje: funkcja fgetwc przyniesie wartość win_t - która jest integralną typu englobing wchar_t (sam wielkości 16 lub 32bits), a utf8 znaków może zajmują od 1 do 4 bajtów (czyli od 8 do 32 bitów) w zwykłym char *. Ponieważ otrzymujesz bezpośrednio wchar_t, nie musisz się martwić kodowaniem utf8.

10

Nowoczesna platforma C powinna zapewniać wszystko, czego potrzeba do wykonania tego zadania.

Pierwszą rzeczą, którą musisz mieć pewność, jest to, że twój program działa w locale, który może obsłużyć utf8. Twój environement powinny być już ustawione, że jedyną rzeczą, którą trzeba zrobić w kodzie jest

setlocale(LC_ALL, ""); 

aby przełączyć się z lokalizacji "C" do swojego naturalnego środowiska.

Następnie można odczytać ciągi jak zwykle z fgets, np. Aby dokonać porównań dla znaków akcentowanych i innych rzeczy, musisz przekonwertować taki ciąg na szeroki ciąg znaków (mbsrtowcs), o czym już wspomniałeś. Kodowanie tak szerokich znaków jest zdefiniowane przez implementację, ale nie musisz wiedzieć, że kodowanie to sprawdza.

Zwykle coś takiego jak L'ä' będzie działać tak długo, jak platforma, na której kompilujesz i gdzie jesteś wykonywany, nie zostanie całkowicie wykręcona. Jeśli potrzebujesz kodów, których nie możesz wprowadzić na klawiaturze, możesz użyć notacji z C11, o czym wspomniał Didierc w swojej odpowiedzi. ('L'\uXXXX' jest do „podstawowych” znaków, jeśli masz coś naprawdę dziwne byłoby użyć L'\UXXXXXXXX', kapitał U z 8 hex-cyfry)

Jak powiedział, kodowanie dla szerokich znaków jest wdrożenie zdefiniowane, ale dobrych szans są to albo utf-16 albo utf-32, które możesz sprawdzić za pomocą sizeof(wchar_t) i wstępnie zdefiniowanego makra __STDC_ISO_10646__. Nawet jeśli twoja platforma obsługuje tylko utf-16 (który może zawierać 2-słowo "znaki") opisywany przypadek użycia nie powinien powodować żadnych problemów, ponieważ wszystkie twoje postacie mogą być kodowane za pomocą formularza L'\uXXXX'.

+1

Nie jestem tego pewien, ale wierzę, że jeśli 'wchar_t' ma 16 bitów, nie może reprezentować punktów kodowych poza BMP. (Innymi słowy, w tym przypadku jest to UCS-2, nie UTF-16, bez dwuliterowych znaków.) – mafso

+1

@mafso, zarówno UCS-2, jak i UTF-16 są możliwe, ale UCS-2 jest rzadkością, obecnie . W każdym razie OP wydaje się być zainteresowany BMP, więc nie powinno to mieć dla niego znaczenia, jak dla większości ludzi. (Wartość '__STDC_ISO_10646__' powinna również wskazywać, która z tych dwóch wartości ma zastosowanie.) –

+1

UTF-16 nie jest możliwym kodowaniem dla' wchar_t', ponieważ API C dla szerokich znaków zasadniczo nie dopuszcza konwersji z wielobajtowego na szerokie postacie do stworzenia więcej niż jednej szerokiej postaci. Możliwe są tylko UTF-32 (UCS-2) i UCS-2. –