天天看點

用 boost regex_replace 把全角數字替換為 單位元組數字(解決問題)

用 boost regex_replace 把全角數字替換為 單位元組數字(解決問題)
用 boost regex_replace 把全角數字替換為 單位元組數字(解決問題)
代碼

string s1 = "(\\xa3\\xb0)|(\\xa3\\xb1)|(\\xa3\\xb2)|(\\xa3\\xb3)|(\\xa3\\xb4)|(\\xa3\\xb5)|(\\xa3\\xb6)|(\\xa3\\xb7)|(\\xa3\\xb8)|(\\xa3\\xb9)"; 
string s2 = "(?1(0))(?2(1))(?3(2))(?4(3))(?5(4))(?6(5))(?7(6))(?8(7))(?9(8))(?10(9))"; 
    boost::regex reg( s1 ); 
string s = boost::regex_replace( string("中國01239大學ef5678"), reg, s2, boost::match_extra| boost::format_all);      

 上述代碼是2010年4月20日寫就的, 本以為已把問題解決但是後來發現若字串中包含

“口福啊!”

“福”的後半個字元與“啊”的第一個字元分别是,A3 B0 這恰好是全角字元“ 0”的代碼,

同理,在一個文本中還可能存在其它類似的巧合情況與  123456789恰好相符,

因為存在這樣的巧合,如果使用上面代碼進行替換操作就會造成替換後的文本出亂碼。

今天,2010年4月21日。

發現下面的文章,其思路是首先把文本轉換為寬字元文本,然後針對寬字元文本使用regex_replace(....),替換完成後,再把寬字元換回多位元組文本。

k.m.Cao

v0.1

問題的提出:

Boost.Regex作為Boost對正規表達式的實踐,是C++開發中常用模式比對工具。但在這次使用過程中發現,它他對中文的支援并不好。當我們指定\w比對時,包含“數”或“節”等字的字元串就會出現比對失敗的問題。

解決方案:

思路:把字元都轉換成寬字元,然後再比對。

需要用到以下和寬字元有關的類:

1、wstring:

作為STL中和string相對應的類,專門用于處理寬字元串。方法和string都一樣,差別是value_type是wchar_t。wstring類的對象要指派或連接配接的常量字元串必須以L開頭标示為寬字元。

2、wregex:

和regex相對應,專門處理寬字元的正規表達式類。同樣可以使用regex_match()和regex_replace()等函數。regex_match()的結果需要放在wsmatch類的對象中。

字元和寬字元的互相轉換:

1、RTL的方法

//把字元串轉換成寬字元串

    setlocale( LC_CTYPE, "" );  // 很重要,沒有這一句,轉換會失敗。

    int iWLen= mbstowcs( NULL, sToMatch.c_str(), sToMatch.length() );  // 計算轉換後寬字元串的長度。(不包含字元串結束符)

    wchar_t *lpwsz= new wchar_t[iWLen+1];

    int i= mbstowcs( lpwsz, sToMatch.c_str(), sToMatch.length() );  // 轉換。(轉換後的字元串有結束符)

    wstring wsToMatch(lpwsz);

    delete []lpwsz;

//把寬字元串轉換成字元串,輸出使用

    int iLen= wcstombs( NULL, wsm[1].str().c_str(), 0 ); // 計算轉換後字元串的長度。(不包含字元串結束符)

    char *lpsz= new char[iLen+1];

    int i= wcstombs( lpsz, wsm[1].str().c_str(), iLen ); // 轉換。(沒有結束符)

    lpsz[iLen] = '\0';

    string sToMatch(lpsz);

    delete []lpsz;

2、Win32 SDK的方法

    int iWLen= MultiByteToWideChar( CP_ACP, 0, sToMatch.c_str(), sToMatch.size(), 0, 0 ); // 計算轉換後寬字元串的長度。(不包含字元串結束符)

    wchar_t *lpwsz= new wchar_t [iWLen+1];

    MultiByteToWideChar( CP_ACP, 0, sToMatch.c_str(), sToMatch.size(), lpwsz, iWLen ); // 正式轉換。

    wsz[iWLen] = L'\0';

    int iLen= WideCharToMultiByte( CP_ACP, NULL, wsResult.c_str(), -1, NULL, 0, NULL, FALSE ); // 計算轉換後字元串的長度。(包含字元串結束符)

    char *lpsz= new char[iLen];

    WideCharToMultiByte( CP_OEMCP, NULL, wsResult.c_str(), -1, lpsz, iLen, NULL, FALSE); // 正式轉換。

    sResult.assign( lpsz, iLen-1 ); // 對string對象進行指派。

MultiByteToWideChar與WideCharToMultiByte詳解

    第一個就是寬字元到多位元組字元轉換函數,函數原型如下:

int WideCharToMultiByte(

UINT CodePage,

DWORD dwFlags,

LPCWSTR lpWideCharStr,

int cchWideChar,

LPSTR lpMultiByteStr,

int cbMultiByte,

LPCSTR lpDefaultChar,

LPBOOL lpUsedDefaultChar

);

此函數把寬字元串轉換成指定的新的字元串,如ANSI,UTF8等,新字元串不必是多位元組字元集。參數:

CodePage: 指定要轉換成的字元集代碼頁,它可以是任何已經安裝的或系統自帶的字元集,你也可以使用如下所示代碼頁之一。

CP_ACP 目前系統ANSI代碼頁

CP_MACCP 目前系統Macintosh代碼頁

CP_OEMCP 目前系統OEM代碼頁,一種原始裝置制造商硬體掃描碼

CP_SYMBOL Symbol代碼頁,用于Windows 2000及以後版本,我不明白是什麼

CP_THREAD_ACP 目前線程ANSI代碼頁,用于Windows 2000及以後版本,我不明白是什麼

CP_UTF7 UTF-7,設定此值時lpDefaultChar和lpUsedDefaultChar都必須為NULL

CP_UTF8 UTF-8,設定此值時lpDefaultChar和lpUsedDefaultChar都必須為NULL

我想最常用的應該是CP_ACP和CP_UTF8了,前者将寬字元轉換為ANSI,後者轉換為UTF8。

dwFlags: 指定如何處理沒有轉換的字元, 但不設此參數函數會運作的更快一些,我都是把它設為0。 可設的值如下表所示:

WC_NO_BEST_FIT_CHARS 把不能直接轉換成相應多位元組字元的Unicode字元轉換成lpDefaultChar指定的預設字元。也就是說,如果把Unicode轉換成多位元組字元,然後再轉換回來,你并不一定得到相同的Unicode字元,因為這期間可能使用了預設字元。此選項可以單獨使用,也可以和其他選項一起使用。

WC_COMPOSITECHECK 把合成字元轉換成預制的字元。它可以與後三個選項中的任何一個組合使用,如果沒有與他們中的任何一個組合,則與選項WC_SEPCHARS相同。

WC_ERR_INVALID_CHARS 此選項會緻使函數遇到無效字元時失敗傳回,并且GetLastError會傳回錯誤碼ERROR_NO_UNICODE_TRANSLATION。否則函數會自動丢棄非法字元。此選項隻能用于UTF8。

WC_DISCARDNS 轉換時丢棄不占空間的字元,與WC_COMPOSITECHECK一起使用

WC_SEPCHARS 轉換時産生單獨的字元,此是預設轉換選項,與WC_COMPOSITECHECK一起使用

WC_DEFAULTCHAR 轉換時使用預設字元代替例外的字元,(最常見的如’?’),與WC_COMPOSITECHECK一起使用。

當指定WC_COMPOSITECHECK時,函數會将合成字元轉換成預制字元。合成字元由一個基字元和一個不占空間的字元(如歐洲國家及漢語拼音的音标)組成,每一個都有不同的字元值。預制字元有一個用于表示基字元和不占空間字元的合成體的單一的字元值。

當指定WC_COMPOSITECHECK選項時,也可以使用上表列出的最後3個選項來定制預制字元的轉換規則。這些選項決定了函數在遇到寬字元串的合成字元沒有對應的預制字元時的行為,他們與WC_COMPOSITECHECK一起使用,如果都沒有指定,函數預設WC_SEPCHARS。

對于下列代碼頁,dwFlags必須為0,否則函數傳回錯誤碼ERROR_INVALID_FLAGS。

50220 50221 50222 50225 50227 50229 52936 54936 57002到57011 65000(UTF7) 42(Symbol)

對于UTF8,dwFlags必須為0或WC_ERR_INVALID_CHARS,否則函數都将失敗傳回并設定錯誤碼ERROR_INVALID_FLAGS,你可以調用GetLastError獲得。

lpWideCharStr: 待轉換的寬字元串。

cchWideChar: 待轉換寬字元串的長度,-1表示轉換到字元串結尾。

lpMultiByteStr: 接收轉換後輸出新串的緩沖區。

cbMultiByte: 輸出緩沖區大小,如果為0,lpMultiByteStr将被忽略,函數将傳回所需緩沖區大小而不使用lpMultiByteStr。

lpDefaultChar: 指向字元的指針, 在指定編碼裡找不到相應字元時使用此字元作為預設字元代替。如果為NULL則使用系統預設字元。對于要求此參數為NULL的dwFlags而使用此參數,函數将失敗傳回并設定錯誤碼 ERROR_INVALID_PARAMETER。

lpUsedDefaultChar:開關變量的指針,用以表明是否使用過預設字元。對于要求此參數為NULL的dwFlags而使用此參數,函數将失敗傳回并設定錯誤碼ERROR_INVALID_PARAMETER。lpDefaultChar和lpUsedDefaultChar都設為NULL,函數會更快一些。

傳回值: 如果函數成功,且cbMultiByte非0,傳回寫入lpMultiByteStr的位元組數(包括字元串結尾的null);cbMultiByte為0,則傳回轉換所需

位元組數。函數失敗,傳回0。

注意:函數WideCharToMultiByte使用不當,會給影響程式的安全。調用此函數會很容易導緻記憶體洩漏,因為lpWideCharStr指向的輸入緩沖區大小是寬字元數,而lpMultiByteStr指向的輸出緩沖區大小是位元組數。為了避免記憶體洩漏,應確定為輸出緩沖區指定合适的大小。我的方法是先使cbMultiByte為0調用WideCharToMultiByte一次以獲得所需緩沖區大小,為緩沖區配置設定空間,然後再次調用 WideCharToMultiByte填充緩沖區,詳見下面的代碼。另外,從Unicode UTF16向非Unicode字元集轉換可能會導緻資料丢失,因為該字元集可能無法找到表示特定Unicode資料的字元。

wchar_t* pwszUnicode = "Holle, word! 你好,中國! ";

int iSize;

char* pszMultiByte;

iSize = WideCharToMultiByte(CP_ACP, 0, pwszUnicode, -1, NULL, 0, NULL, NULL);

pszMultiByte = (char*)malloc((iSize+1)/**sizeof(char)*/);

WideCharToMultiByte(CP_ACP, 0, pwszUnicode, -1, pszMultiByte, iSize, NULL, NULL);

第二個是多位元組字元到寬字元轉換函數,函數原型如下:

> int MultiByteToWideChar(

LPCSTR lpMultiByteStr,

LPWSTR lpWideCharStr,

int cchWideChar

此函數把多位元組字元串轉換成寬字元串(Unicode),待轉換的字元串并不一定是多位元組的。

此函數的參數,傳回值及注意事項參見上面函數WideCharToMultiByte的說明,這裡隻對dwFlags做簡單解釋。

dwFlags: 指定是否轉換成預制字元或合成的寬字元,對控制字元是否使用像形文字,以及怎樣處理無效字元。

MB_PRECOMPOSED 總是使用預制字元,即有單個預制字元時,就不會使用分解的基字元和不占空間字元。此為函數的預設選項,不能和MB_COMPOSITE合用

MB_COMPOSITE 總是使用分解字元,即總是使用基字元+不占空間字元的方式

MB_ERR_INVALID_CHARS 設定此選項,函數遇到非法字元就失敗并傳回錯誤碼ERROR_NO_UNICODE_TRANSLATION,否則丢棄非法字元

MB_USEGLYPHCHARS 使用像形字元代替控制字元

對于UTF8,dwFlags必須為0或MB_ERR_INVALID_CHARS,否則函數都将失敗并傳回錯誤碼ERROR_INVALID_FLAGS。

以下函數我沒用過,隻簡要說明之。

int GetTextCharset( HDC hdc );

此函數擷取目前選進的裝置描述表的字元集,等同于GetTextCharsetInfo(hdc, NULL, 0)。

傳回值: 成功傳回字元集辨別,失敗傳回DEFAULT_CHARSET。

示例:

通過以下程式我們可以看到,對字元串做\w比對時,某些字會引起比對失敗。通過把字元串轉換成寬字元串嘗試解決這個問題。

#include <iostream>

using std::cout;

using std::endl;

#include <string>

using std::string;

using std::wstring;

#include <locale>

#include "boost\tr1\regex.hpp"

using namespace boost;

void MatchWords(string sToMatch)

{

    regex rg("(\\w*)");

    smatch sm;

    regex_match( sToMatch, sm, rg );

    cout << "比對結果:" << sm[1].str() << endl;

}

void MatchWords(wstring wsToMatch)

    wregex wrg(L"(\\w*)");

    wsmatch wsm;

    regex_match( wsToMatch, wsm, wrg );

    int iLen= wcstombs( NULL, wsm[1].str().c_str(), 0 );

    int i= wcstombs( lpsz, wsm[1].str().c_str(), iLen );

    cout << "比對結果:" << sToMatch << endl;

void main()

    string sToMatch("數超限");

    MatchWords( sToMatch );

    sToMatch = "節點數目超限";

    setlocale( LC_CTYPE, "" );

    int iWLen= mbstowcs( NULL, sToMatch.c_str(), sToMatch.length() );

    int i= mbstowcs( lpwsz, sToMatch.c_str(), sToMatch.length() );

    MatchWords( wsToMatch );

編譯執行程式後輸出:

   比對結果:數超限

    比對結果:

    比對結果:節點數目超限

第一行顯示“數超限”比對成功。但第二行“節點數超限”沒有比對到任何字元。隻有轉換成寬字元串之後才能夠對“節點數超限”成功進行\w比對。

根據上面的代碼改造的可以在VC 6.0下運作的代碼如下:

CString zs = "0";

WCHAR * strDest = NULL;

int iSize = MultiByteToWideChar(CP_ACP, 0, (LPCSTR)sz, -1, NULL);

//使用兩次ultiByteToWideChar,第一次隻為擷取大小

strDest = new WCHAR[iSize + 1];

MultiByteToWideChar(CP_ACP, 0 , (LPCSTR)s2, -1, strDest, iSize*sizeof(wchar_t));

boost::wregx wrg(L"0");

std::wstring strRet = boost::regex_replace(wsToMatch,wrg, strDest, boost::match_default|boost::format_all);

int iLen =  wcstombs(NULL, strRet.c_str(), 0);

char * lpsz = new char[iLen + 1];

int iii =  wcstombs(lpsz, strRet.c_str(), iLen);

lpsz[iLen] = '\0';