ftell/fgetpos не может получить правильную позицию при обработке текстового файла UTF-8
Тест под VStudio 2012 + Win7
Текстовый файл UTF-8 содержит всего 5 байтов:
31 0a 32 0a 0a
в текстовом режиме это будет показано так:
1
2
Источник также прост:
FILE *fp;
TCHAR buf[100] ={0};
TCHAR *line;
LONG pos;
_tfopen_s(&fp, _T("...\\test.txt"), _T("r,ccs=UTF-8"));
line = _fgetts(buf, 100, fp);
pos = ftell(fp);
if(fseek(fp, pos, SEEK_SET)!=0)
perror( "fseek error");
line = _fgetts(buf, 100, fp);
pos = ftell(fp);
fclose(fp);
Однако при отладке программы 1-й ftell()
возвращает значение позиции 1 вместо 2... Так что когда _fgetts()
вызывается для 2-ой текстовой строки, она просто получает знак CR вместо символа 2
,
Интересно, есть ли некомпетентность в обработке файла в "r,ccs=UTF-8"
текстовый режим (образец хорошо работает в "r"
mode (EDIT: NOT true! 1st ftell () возвращает 0. Спасибо Гансу за указание)).
(Это еще более странно, что ftell()
работает правильно, когда текстовый файл UTF-8 содержит любые не-ANSI символы... но давайте сначала решим чистый ANSI-файл. И да, я уже искал по форуму, но на удивление не нашел подобного спрашивающего)
Лучший обходной путь пока читает строки в "r"
режим, а затем переводить их из кодировки UTF-8 в Unicode. Любое более умелое предложение будет действительно оценено.
----- ОБНОВЛЕНИЕ делителя (2015/03/25) -----
Тест под MinGW + Win7 и GCC + CentOS
Получив ценные комментарии по следующим ключевым моментам,
- реализация компилятора: Microsoft против GNU @nm
неточное использование внутреннего буфера для
ftell()
: @Hans Passant- кодирование с фиксированной длиной (например, режим "r") и кодирование с переменной длиной (например, режим "r,css=UTF-8")
- Конец строки с 1 символом (одиночный LF) и конец строки с 2 символами (CR+LF) @Hans Passant, @IInspectable
Я решил проверить проблему в сложных условиях.
Текстовые файлы, используемые
line-feed ANSI/mixed BOM encoding
1.txt single-LF pure n/a UTF-8
2.txt CR-LF* pure n/a UTF-8
3.txt CR-LF* mixed n/a UTF-8
4.txt CR-LF* mixed EFBBBF UTF-8
5.txt CR-LF* mixed FFFE UTF-16
* Except for tests under CentOS, which use single-LF only.
Используемый источник (для компилятора GNU)
FILE *fp;
wchar_t buf[100] ={0};
wchar_t *line;
long pos;
//setlocale(LC_CTYPE, "en_GB.UTF-8"); //uncomment this for GNU+CentOS
fp = fopen("....txt", "r"); //or "r,ccs=UTF-8"
pos = ftell(fp);
if(fseek(fp, pos, SEEK_SET)!= 0)
perror( "fseek error" );
line = fgetws(buf, 100, fp);
pos = ftell(fp);
if(fseek(fp, pos, SEEK_SET)!= 0) //breakpoint, check result of ftell()
perror( "fseek error" );
line = fgetws(buf, 100, fp);
pos = ftell(fp);
fclose(fp);
Результат № 1: режим "r", GNU+Win7
1.txt(single LF): pos=0, NG `Really failed!(@Hans Passant, @IInspectable)
2.txt(pure ANSI): pos=7, OK
3.txt(non-ANSI): pos=13, OK(String is UTF-8 encoded)
4.txt(BOM=EFBBBF,UTF-8): pos=9, NG(BOM is also read)
5.txt(BOM=FFFE,UTF-16): pos=9, NG(BOM is also read)
Результат № 2: режим "r,ccs=UTF-8", GNU+Win7, с / без setlocale()
1.txt(single LF): pos=-3!, NG(1st line can be read, UTF-16="\0x31\0xa")
2.txt(pure ANSI): pos=0, NG(1st line can be read, UTF-16=L"1abcd\n")
3.txt(non-ANSI): pos=8, NG(1st line can be read, UTF-16. but 2nd line is incorrect!)
4.txt(BOM=EFBBBF,UTF-8): pos=9, OK!(BOM ignored, String is UTF-16 = "\0x31\0x4f60\0xa". 2nd line is "\0x32\0x597d")
5.txt(BOM=FFFE,UTF-16): pos=10, OK!(BOM ignored, String is UTF-16 = "\0x31\0x4f60\0xa". 2nd line is "\0x32\0x597d")
Результат № 3: режим "r,ccs=UTF-8", GNU + CentOS, WITH setlocale()
1.txt(single LF): pos=2, OK
2.txt(pure ANSI): pos=6, OK
3.txt(non-ANSI): pos=12, OK
4.txt(BOM=EFBBBF,UTF-8): not tested
5.txt(BOM=FFFE,UTF-16): not tested
Заключение
- Для GNU+CentOS, если (и только если)
setlocale()
используется,ftell()
работает отлично. Я предполагаю, что это потому, что конец строки с одним LF является стандартным в Unix. - Для Windows, однако, если вы используете один-LF или
"ccs=UTF-8"
Режим,ftell()
даст вам неточное возвращаемое значение без предупреждения...setlocale()
Здесь нет никакой разницы. Однако прикрепленные к спецификации файлы UTF-8/UTF-16 могут обрабатываться идеально... что означаетftell()
может иметь потенциальную способность в обработке кодирования переменной длины??
Наконец, как уже упоминалось ранее,"r"
режим (с соблюдением правила окончания строки CR+LF) "спасет мир". ~
@ Ханс Пассант, @nm, пожалуйста, измените заключение, если я что-то пропустил.