如何逐行读取Unicode(UTF-8)/二进制文件?

14

各位程序员,

我想按行读取由记事本创建的Unicode(UTF-8)文本文件,我不想在屏幕上显示Unicode字符串,我只想读取和比较字符串。

以下代码按行读取ANSI文件,并比较字符串

我的需求

按行读取 test_ansi.txt 文件

如果该行 =“b”,则输出“YES!”

否则输出“NO!”

read_ansi_line_by_line.c

#include <stdio.h>

int main()
{
    char *inname = "test_ansi.txt";
    FILE *infile;
    char line_buffer[BUFSIZ]; /* BUFSIZ is defined if you include stdio.h */
    char line_number;

    infile = fopen(inname, "r");
    if (!infile) {
        printf("\nfile '%s' not found\n", inname);
        return 0;
    }
    printf("\n%s\n\n", inname);

    line_number = 0;
    while (fgets(line_buffer, sizeof(line_buffer), infile)) {
        ++line_number;
        /* note that the newline is in the buffer */
        if (strcmp("b\n", line_buffer) == 0 ){
            printf("%d: YES!\n", line_number);
        }else{
            printf("%d: NO!\n", line_number,line_buffer);
        }
    }
    printf("\n\nTotal: %d\n", line_number);
    return 0;
}

test_ansi.txt

a
b
c

编译

gcc -o read_ansi_line_by_line read_ansi_line_by_line.c

输出

test_ansi.txt

1: NO!
2: YES!
3: NO!


Total: 3

现在我需要读取由记事本创建的Unicode(UTF-8)文件,但是经过超过6个月的搜索,我没有找到任何能够读取UTF-8编码文件的好的C代码/库!我不知道确切的原因,但我认为标准C不支持Unicode!

读取Unicode二进制文件没问题!但问题在于二进制文件必须已经以二进制模式创建!这意味着,如果我们要读取由记事本创建的Unicode(UTF-8)文件,我们需要将其从UTF-8文件转换为二进制文件!

此代码将Unicode字符串写入二进制文件,注意C文件以UTF-8编码,并由GCC编译。

我想要的

将Unicode字符“ب”写入test_bin.dat

create_bin.c

#define UNICODE
#ifdef UNICODE
#define _UNICODE
#else
#define _MBCS
#endif

#include <stdio.h>
#include <wchar.h>

int main()
{
     /*Data to be stored in file*/
     wchar_t line_buffer[BUFSIZ]=L"ب";
     /*Opening file for writing in binary mode*/
     FILE *infile=fopen("test_bin.dat","wb");
     /*Writing data to file*/
     fwrite(line_buffer, 1, 13, infile);
     /*Closing File*/
     fclose(infile);

    return 0;
}

编译

gcc -o create_bin create_bin.c

输出

create test_bin.dat

现在我想逐行读取二进制文件并进行比较!

我的需求

逐行读取test_bin.dat文件 如果行等于"ب"则打印"YES!" 否则打印"NO!"

read_bin_line_by_line.c

#define UNICODE
#ifdef UNICODE
#define _UNICODE
#else
#define _MBCS
#endif

#include <stdio.h>
#include <wchar.h>

int main()
{
    wchar_t *inname = L"test_bin.dat";
    FILE *infile;
    wchar_t line_buffer[BUFSIZ]; /* BUFSIZ is defined if you include stdio.h */

    infile = _wfopen(inname,L"rb");
    if (!infile) {
        wprintf(L"\nfile '%s' not found\n", inname);
        return 0;
    }
    wprintf(L"\n%s\n\n", inname);

    /*Reading data from file into temporary buffer*/
    while (fread(line_buffer,1,13,infile)) {
        /* note that the newline is in the buffer */
        if ( wcscmp ( L"ب" , line_buffer ) == 0 ){
             wprintf(L"YES!\n");
        }else{
             wprintf(L"NO!\n", line_buffer);
        }
    }
    /*Closing File*/
    fclose(infile);
    return 0;
}

输出

test_bin.dat

YES!

问题

这个方法太长了!而且不够强大(我是软件工程的初学者)

请问有谁知道如何读取Unicode文件?(我知道这不容易!) 请问有谁知道如何将Unicode文件转换为二进制文件?(简单方法) 请问有谁知道如何以二进制模式读取Unicode文件?(我不确定)

谢谢。

6个回答

6
UTF-8的一个优点是你无需解码就可以进行比较。从strcmp返回的顺序无论你是否先解码它都是相同的。因此,只需将其读取为原始字节并运行strcmp即可。

5
我找到了一个解决方案,想分享给任何对在 C99 中读取 UTF-8 文件感兴趣的人。请看以下内容:

void ReadUTF8(FILE* fp)
{
    unsigned char iobuf[255] = {0};
    while( fgets((char*)iobuf, sizeof(iobuf), fp) )
    {
            size_t len = strlen((char *)iobuf);
            if(len > 1 &&  iobuf[len-1] == '\n')
                iobuf[len-1] = 0;
            len = strlen((char *)iobuf);
            printf("(%d) \"%s\"  ", len, iobuf);
            if( iobuf[0] == '\n' )
                printf("Yes\n");
            else
                printf("No\n");
    }
}

void ReadUTF16BE(FILE* fp)
{
}

void ReadUTF16LE(FILE* fp)
{
}

int main()
{
    FILE* fp = fopen("test_utf8.txt", "r");
    if( fp != NULL)
    {
        // see http://en.wikipedia.org/wiki/Byte-order_mark for explaination of the BOM
        // encoding
        unsigned char b[3] = {0};
        fread(b,1,2, fp);
        if( b[0] == 0xEF && b[1] == 0xBB)
        {
            fread(b,1,1,fp); // 0xBF
            ReadUTF8(fp);
        }
        else if( b[0] == 0xFE && b[1] == 0xFF)
        {
            ReadUTF16BE(fp);
        }
        else if( b[0] == 0 && b[1] == 0)
        {
            fread(b,1,2,fp); 
            if( b[0] == 0xFE && b[1] == 0xFF)
                ReadUTF16LE(fp);
        }
        else
        {
            // we don't know what kind of file it is, so assume its standard
            // ascii with no BOM encoding
            rewind(fp);
            ReadUTF8(fp);
        }
    }        

    fclose(fp);
}

2

fgets()可以解码UTF-8编码的文件,如果您使用的是Visual Studio 2005或更高版本。将您的代码更改为以下内容:

infile = fopen(inname, "r, ccs=UTF-8");

nobugz,我在这个项目中可以使用GCC/C99,有什么方法可以在GCC中使用您的解决方案吗? :)谢谢你,nobugz。 - Freeseif
我怀疑,我对gcc CRT了解不够深入。你可以试一下。 - Hans Passant
请不要这样做 - 这会阻止代码在其他平台/编译器下以正确的方式运行。 - elcuco

2
在本文中,编写了一种编码和解码例程,并解释了Unicode是如何编码的。

http://www.codeguru.com/cpp/misc/misc/multi-lingualsupport/article.php/c10451/

它可以轻松地调整为C。 只需对您的ANSI进行编码或解码UTF-8字符串并进行字节比较即可。
编辑:在OP表示太难从C ++重新编写该功能后,这里提供一个模板:
需要什么:
+ 释放分配的内存(或等到进程结束或忽略它)
+ 添加4字节函数
+ 告诉我short和int不能保证为2和4个字节长(我知道,但C确实很蠢!),最后
+ 找到一些其他错误
#include <stdlib.h>
#include <string.h>

#define         MASKBITS                0x3F
#define         MASKBYTE                0x80
#define         MASK2BYTES              0xC0
#define         MASK3BYTES              0xE0
#define         MASK4BYTES              0xF0
#define         MASK5BYTES              0xF8
#define         MASK6BYTES              0xFC

char* UTF8Encode2BytesUnicode(unsigned short* input)
{
   int size = 0,
       cindex = 0;
   while (input[size] != 0)
     size++;
   // Reserve enough place; The amount of 
   char* result = (char*) malloc(size);
   for (int i=0; i<size; i++)
   {
      // 0xxxxxxx
      if(input[i] < 0x80)
      {
         result[cindex++] = ((char) input[i]);
      }
      // 110xxxxx 10xxxxxx
      else if(input[i] < 0x800)
      {
         result[cindex++] = ((char)(MASK2BYTES | input[i] >> 6));
         result[cindex++] = ((char)(MASKBYTE | input[i] & MASKBITS));
      }
      // 1110xxxx 10xxxxxx 10xxxxxx
      else if(input[i] < 0x10000)
      {
         result[cindex++] = ((char)(MASK3BYTES | input[i] >> 12));
         result[cindex++] = ((char)(MASKBYTE | input[i] >> 6 & MASKBITS));
         result[cindex++] = ((char)(MASKBYTE | input[i] & MASKBITS));
      }
   }
}

wchar_t* UTF8Decode2BytesUnicode(char* input)
{
  int size = strlen(input);
  wchar_t* result = (wchar_t*) malloc(size*sizeof(wchar_t));
  int rindex = 0,
      windex = 0;
  while (rindex < size)
  {
      wchar_t ch;

      // 1110xxxx 10xxxxxx 10xxxxxx
      if((input[rindex] & MASK3BYTES) == MASK3BYTES)
      {
         ch = ((input[rindex] & 0x0F) << 12) | (
               (input[rindex+1] & MASKBITS) << 6)
              | (input[rindex+2] & MASKBITS);
         rindex += 3;
      }
      // 110xxxxx 10xxxxxx
      else if((input[rindex] & MASK2BYTES) == MASK2BYTES)
      {
         ch = ((input[rindex] & 0x1F) << 6) | (input[rindex+1] & MASKBITS);
         rindex += 2;
      }
      // 0xxxxxxx
      else if(input[rindex] < MASKBYTE)
      {
         ch = input[rindex];
         rindex += 1;
      }

      result[windex] = ch;
   }
}

char* getUnicodeToUTF8(wchar_t* myString) {
  int size = sizeof(wchar_t);
  if (size == 1)
    return (char*) myString;
  else if (size == 2)
    return UTF8Encode2BytesUnicode((unsigned short*) myString);
  else
    return UTF8Encode4BytesUnicode((unsigned int*) myString);
}

Thorsten S. 调整了这个冗长的 C++ 函数,需要一个高级的 C/C++ 程序员 xD。谢谢 Thorsten S. - Freeseif

2
我知道我很糟糕……但你甚至不考虑字节顺序标记!这里的大多数示例都会失败。
编辑:
字节顺序标记是文件开头的几个字节,可用于识别文件的编码。一些编辑器会添加它们,而且很多时候它们会以惊人的方式破坏事情(我记得因为这个问题而与PHP标题问题战斗了几分钟)。
一些RTFM: http://en.wikipedia.org/wiki/Byte_order_mark http://blogs.msdn.com/oldnewthing/archive/2004/03/24/95235.aspx 什么是XML BOM,如何检测?

4
哎呀,由于UTF-8是一种字节格式,所以不需要字节顺序标记。很抱歉我破坏了你的恶意。 - Thorsten S.
Elcuco,你能否请再给我解释一下吗?:) 谢谢你,Elcuco。 - Freeseif
@Thorsten S.,仅因为没有字节顺序标记的需要并不意味着你不会得到一个。我今天刚遇到一个,可能是由记事本产生的。维基百科承认可以使用它来将文件标记为UTF-8,尽管这并不推荐。 - Mark Ransom
这意味着我们需要忽略第一行吗? 换句话说,有没有C++中的任何好的解决方案(简单)?! - Freeseif
1
不,你需要忽略第一行的前三个字节。但是只有当它们按顺序为0xEF 0xBB 0xBF时,你才需要忽略;如果它们不是这些字节,则没有BOM,你需要使用整行。(如果文件是用记事本保存的,则始终会在UTF-8中获得BOM。其他编辑器可能会有所不同。) - Michael Madsen
显示剩余2条评论

0

只是为了解决BOM争论。这里有一个来自记事本的文件。

 [paul@paul-es5 tests]$ od -t x1 /mnt/hgfs/cdrive/test.txt
 0000000 ef bb bf 61 0d 0a 62 0d 0a 63
 0000012

在开头加上BOM

个人认为不应该有BOM(因为它是一个字节格式),但这不是重点。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接