Unicode文件解析方法及存在问题

82 阅读2分钟

问题背景

在项目中遇到需要将一个Unicode编码(UTF-16 LE)的文件内容解析出来,但是在使用MFC的类CStdioFile进行文件读取的时候,出现乱码。 在这里插入图片描述 同时,读取的文件有可能还是ASCII编码的,使用CStdioFile读取是可以正常读取。需要同时兼容两个编码。

问题分析

1、Unicode编码的文件其文件前两个字节是0xEF FF,可以以此来判定文件编码格式。 2、将文件以字节流的方式读取出来后,使用MultiBytesToWideChar可以转换成可见字符串。

解决方案

1、既然CStdioFile可以处理ASCII编码,那么可以派生一个类CMyStdioFile专门用于处理Unicode编码。即:CStdioFile处理ASCII编码,CMyStdioFile处理Unicode编码。 2、在打开文件的时候顺便将文件编码判定出来,保存在类成员中,后面使用

代码

#pragma once
#include "afx.h"
#include <string>

//用来处理Unicode格式的FORM文件读取
class CMyStdioFile :
	public CStdioFile
{
public:
	CMyStdioFile();
	virtual ~CMyStdioFile();
public:
	//打开文件,内部会判断文件是Unicode还是ASCII
	virtual BOOL Open(LPCTSTR lpszFileName, UINT nOpenFlags, CFileException* pError = NULL);

	//读取下一行的数据,可以支持Unicode文件和ASCII文件
	virtual BOOL ReadString(CString& rString);
private:
	bool		m_bUnicode;			//是否为Unicode文件
};





#include "stdafx.h"
#include "MyStdioFile.h"


CMyStdioFile::CMyStdioFile()
{
	
}


CMyStdioFile::~CMyStdioFile()
{
}


//读取下一行的数据,可以支持Unicode文件和ASCII文件
BOOL CMyStdioFile::ReadString(CString& rString)
{
	if (m_bUnicode)
	{
		//如果文件是unicode编码,需要进行转换
		wchar_t l_awcLine[256] = { 0 };	//假设一行最多512个字符
		if (!CStdioFile::ReadString((LPTSTR)l_awcLine, 255))
		{
			return FALSE;
		}

		int l_iConvertResult = WideCharToMultiByte(CP_ACP, 0, l_awcLine, -1, NULL, 0, NULL, NULL);
		if (l_iConvertResult <= 0)
		{
			return FALSE;
		}
		std::string l_strLine;
		l_strLine.resize(l_iConvertResult + 1);
		l_iConvertResult = WideCharToMultiByte(CP_ACP, 0, l_awcLine, -1, &l_strLine[0], l_iConvertResult, NULL, NULL);
		if (l_iConvertResult <= 0)
		{
			return FALSE;;
		}
		rString = l_strLine.c_str();
		rString.Trim("\r\n\t");
		rString.Trim();
		Seek(1, CFile::current);
		return TRUE;
	}
	else
	{
		return CStdioFile::ReadString(rString);
	}
}

BOOL CMyStdioFile::Open(LPCTSTR lpszFileName, UINT nOpenFlags, CFileException* pError /*= NULL*/)
{
	BOOL l_bRet = CStdioFile::Open(lpszFileName, nOpenFlags, pError);

	//判断文件的编码格式
	unsigned char l_acLine[512] = { 0 };
	CStdioFile::ReadString((LPTSTR)l_acLine, 511);
	if (l_acLine[0] == 0xFF && l_acLine[1] == 0xFE)	//Little Endian
	{
		m_bUnicode = true;
		Seek(2, CFile::begin);
	}
	else
	{
		m_bUnicode = false;
		Seek(0, CFile::begin);	//移到起始位置供调用者从头一行一行读
	}

	return l_bRet;
}


```cpp调用的地方
CMyStdioFile fFile;	
	if(!fFile.Open(szFormFileName, CFile::modeRead | CFile::typeBinary))
	{
		g_oSpLog.bLogByFormat(LogLevelInternalFail, "FORM文件(%s)打开失败", szFormFileName);
		return -1;
	}

注意事项

1、打开文件时,一定要以Binary的模式进行。