之前用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()帶漢字的內容也是成功的了
這裏的主要問題就是字符的編碼,確實有點麻煩,我都快被繞暈了