处理UTF-8文本文件时,ftell / fgetpos无法获得正确的位置

时间:2015-03-24 10:01:20

标签: c winapi utf-8 ftell

在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);

然而,在调试程序时,第一个ftell()返回的位置值为1而不是2 ...所以当_fgetts()被调用第二个文本行时,它只会得到一个CR标记而不是字符2

我想知道在"r,ccs=UTF-8"文字模式下处理文件是否不称职(该示例在"r"模式下运行良好编辑:不是真的!第一个ftell ()返回0.感谢Hans指出)(甚至更奇怪的是当UTF-8文本文件包含任何非ANSI字符时ftell()正常工作......但是让我们首先解决纯ANSI文件。是的我已经在论坛中搜索过了,但令人惊讶的是没有找到类似的提问者)

最佳解决方法现在是在"r"模式下读取字符串行,然后将它们从UTF-8编码转换为Unicode格式。任何更有技巧的建议都会非常感激。


----- UPDATE divider(2015/03/25)-----

在MinGW + Win7和GCC + CentOS下进行测试

收到有关以下要点的宝贵意见后,

  • 编译器实现:Microsoft vs GNU @n.m。
  • ftell()内部缓冲区用途不准确: @Hans Passant

    • 固定长度编码(例如," r"模式)与可变长度编码 (例如," r,css = UTF-8"模式)
    • 1-char行结束(单个LF)vs 2-char行结束(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()此处显示没有区别。但是,BOM附加的UTF-8 / UTF-16文件可以完美处理...这意味着ftell()可能具有处理可变长度编码的潜力? 最后,正如之前提到的,"r"模式(符合CR + LF行结束规则)将“拯救世界"。”

@Hans Passant,@ n.m。如果我遗漏了任何内容,请修改结论。

0 个答案:

没有答案