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, пожалуйста, измените заключение, если я что-то пропустил.

0 ответов

Другие вопросы по тегам