vs2015 mfc中用IXMLDOMDocumentPtr 讀取帶有漢字的xml文件

之前用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()帶漢字的內容也是成功的了

這裏的主要問題就是字符的編碼,確實有點麻煩,我都快被繞暈了

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章