之前用IXMLDOMDocumentPtr写了个小工具,读取xml文件后,对它进行处理,比如修改项、删除项啥的,然后保存。
今天有人反馈说有 bug,有的文件没有修改。拿来一看,确实没有修改,跟踪发现,load()文件失败。代码流程如下:
int CheckXmlFile(CString strFileName)
{
IXMLDOMDocumentPtr xmlDoc;
HRESULT hr = xmlDoc.CreateInstance(__uuidof(DOMDocument60));
VARIANT_BOOL bSuccess = FALSE;
hr = xmlDoc->load(CComVariant(strFileName), &bSuccess);
if (FAILED(hr) || !bSuccess)
return -3;
... // 处理代码
return 0;
}
工程采用UNICDOE,所以strFileName是unicode字符串
发现这里返回了3,load()失败。然后用notepad++打开这个xml文件,一看里面有个值带有汉字,晕。然后在notepad++上看编码,一看是 ANSI 编码,然后通过编码菜单“转为 UTF-8 编码”,然后保存。再来查看程序,这次发现load()成功了。
这样也麻烦,从xml文件的来源上,没法控制它的原始编码,看来还得自己处理。
我的处理办法就是如果这里load()失败,就自己读取文件然后转码,再进行loadXML():
int CheckXmlFile(CString strFileName)
{
IXMLDOMDocumentPtr xmlDoc;
HRESULT hr = xmlDoc.CreateInstance(__uuidof(DOMDocument60));
VARIANT_BOOL bSuccess = FALSE;
hr = xmlDoc->load(CComVariant(strFileName), &bSuccess);
if (FAILED(hr) || !bSuccess)
{
// 参考 https://blog.csdn.net/zeuskaaba/article/details/4082826
VARIANT vtName;
vtName.vt = VT_BSTR;
CString fileContString;
int fileLen = gReadTextFile(strFileName, fileContString);
if (fileLen < 7)
return -3; // 由于我这里xml文件肯定有一些内容,所以如果内容过短,我就直接返回不处理了
vtName.bstrVal = fileContString.AllocSysString();
hr = xmlDoc->loadXML(vtName.bstrVal, &bSuccess);
SysFreeString(vtName.bstrVal);
if (FAILED(hr) || !bSuccess)
return -3;
}
... // 处理代码
return 0;
}
这里关键的就是gReadTextFile()函数了,因为我的工程中还需要读取其它的文本文件,所以我就写了这个通用函数。另外我的文本文件都不会过长,所以就直接用一个CString把内容返回出来。
这个gReadTextFile()中,先读取文件,然后判断文件编码类型,然后再确定如何转成unicode编码,其中参考了很多其它的网址,下面直接上代码:
CString gStr2U(const CStringA str, CString& strOut, bool useAnsiPage /*= false*/)
{
USES_CONVERSION;
if (useAnsiPage)
_acp = CP_ACP; // 使用默认的线程值,会在有的电脑上失败,即转换出来的内容完全不对
strOut = A2W(str);
return strOut;
}
/*
https://www.cnblogs.com/AkazaAkari/p/8686327.html
*/
#define CHECK_LENGTH 1024000
int is_utf8_string(char *utf)
{
int length = strlen(utf);
int check_sub = 0;
int i = 0;
if (length > CHECK_LENGTH) //只取前面特定长度的字符来验证即可
{
length = CHECK_LENGTH;
}
for (; i < length; i++)
{
if (check_sub == 0)
{
if ((utf[i] >> 7) == 0) //0xxx xxxx
{
continue;
}
else if ((utf[i] & 0xE0) == 0xC0) //110x xxxx
{
check_sub = 1;
}
else if ((utf[i] & 0xF0) == 0xE0) //1110 xxxx
{
check_sub = 2;
}
else if ((utf[i] & 0xF8) == 0xF0) //1111 0xxx
{
check_sub = 3;
}
else if ((utf[i] & 0xFC) == 0xF8) //1111 10xx
{
check_sub = 4;
}
else if ((utf[i] & 0xFE) == 0xFC) //1111 110x
{
check_sub = 5;
}
else
{
return 0;
}
}
else
{
if ((utf[i] & 0xC0) != 0x80)
{
return 0;
}
check_sub--;
}
}
return 1;
}
/*
作者:游学四方
来源:CSDN
原文:https://blog.csdn.net/blackbattery/article/details/78244178
版权声明:本文为博主原创文章,转载请附上博文链接!
//*/
CString g_strUString;
CString gUTF82WCS(const char* szU8)
{
//预转换,得到所需空间的大小;
int wcsLen = ::MultiByteToWideChar(CP_UTF8, NULL, szU8, strlen(szU8), NULL, 0);
//分配空间要给'\0'留个空间,MultiByteToWideChar不会给'\0'空间
wchar_t* wszString = new wchar_t[wcsLen + 1];
//转换
::MultiByteToWideChar(CP_UTF8, NULL, szU8, strlen(szU8), wszString, wcsLen);
//最后加上'\0'
wszString[wcsLen] = '\0';
g_strUString = wszString;
/*
CString unicodeString(wszString);
delete[] wszString;
wszString = NULL;
return unicodeString;
/*/
return g_strUString;
//*/
}
/*
读取一个文本文件
返回值:
0 - 打开失败,或者文件为空
>0 :成功读取的文件长度,单位:字节
*/
int gReadTextFile(CString strFileName, CString& fileContString)
{
FILE* filePtr = _tfopen(strFileName.GetString(), TEXT("r+b"));
strFileName.ReleaseBuffer();
if (filePtr == NULL)
return 0;
fseek(filePtr, 0, SEEK_END);
int fileLen = ftell(filePtr) + 16;
fseek(filePtr, 0, SEEK_SET);
char* fileContPtr = new char[fileLen];
memset(fileContPtr, 0, fileLen);
fread(fileContPtr, 1, fileLen, filePtr);
fclose(filePtr);
filePtr = NULL;
int retv = strlen(fileContPtr);
if (is_utf8_string(fileContPtr))
fileContString = gUTF82WCS(fileContPtr);
else
gStr2U(fileContPtr, fileContString, true);
delete[] fileContPtr;
fileContPtr = NULL;
return retv;
}
其中is_utf8_string()函数,其判断的长度可以根据实际的项目进行修改,对我的项目来说,这个需要判断长度一般有个1KB就完全足够了,但这里为了保险,我改到了最长将近1MB。
思路就是选按照ANSI编码读取,然后判断编码类型是否是utf-8,如果是,就用这个方法来转,否则就用普通的方法来转。这里只判断了两种情形,估计不能应付所有情况,看我另外一个工程中的判断,多了一个分支:
int fenc;
memcpy(&fenc, fileContPtr, 2);
if (fenc != 0xfeff)
{
if (is_utf8_string(fileContPtr))
fileContString = gUTF82WCS(fileContPtr);
else
gStr2U(fileContPtr, fileContString, true);
}
else
{
// 直接就是unicode了,所以就不用再转换了
fileContString = (TCHAR*)fileContPtr;
}
最后的这个转换,差不多可以应付绝大多数的编码转换了,自从我改成这样的判断之后,那个工程就没有再反馈过问题了。
这么折腾一下,loadXML()带汉字的内容也是成功的了
这里的主要问题就是字符的编码,确实有点麻烦,我都快被绕晕了