用 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(....),替换完成后,再把宽字符换回多字节文本。

 http://blog.csdn.net/skyremember/archive/2008/09/17/2941295.aspx

http://www.cnblogs.com/cy163/archive/2010/04/20/1716695.html

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(
UINT CodePage,
DWORD dwFlags,
LPCSTR lpMultiByteStr,
int cbMultiByte,
LPWSTR lpWideCharStr,
int cchWideChar
);

此函数把多字节字符串转换成宽字符串(Unicode),待转换的字符串并不一定是多字节的。

此函数的参数,返回值及注意事项参见上面函数WideCharToMultiByte的说明,这里只对dwFlags做简单解释。

dwFlags: 指定是否转换成预制字符或合成的宽字符,对控制字符是否使用像形文字,以及怎样处理无效字符。

MB_PRECOMPOSED 总是使用预制字符,即有单个预制字符时,就不会使用分解的基字符和不占空间字符。此为函数的默认选项,不能和MB_COMPOSITE合用
MB_COMPOSITE 总是使用分解字符,即总是使用基字符+不占空间字符的方式
MB_ERR_INVALID_CHARS 设置此选项,函数遇到非法字符就失败并返回错误码ERROR_NO_UNICODE_TRANSLATION,否则丢弃非法字符
MB_USEGLYPHCHARS 使用像形字符代替控制字符

对于下列代码页,dwFlags必须为0,否则函数返回错误码ERROR_INVALID_FLAGS。
50220 50221 50222 50225 50227 50229 52936 54936 57002到57011 65000(UTF7) 42(Symbol)
对于UTF8,dwFlags必须为0或MB_ERR_INVALID_CHARS,否则函数都将失败并返回错误码ERROR_INVALID_FLAGS。

以下函数我没用过,只简要说明之。

int GetTextCharset( HDC hdc );

此函数获取当前选进的设备描述表的字符集,等同于GetTextCharsetInfo(hdc, NULL, 0)。

返回值: 成功返回字符集标识,失败返回DEFAULT_CHARSET。


本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/ddl007/archive/2009/02/23/3929993.aspx

 

示例:

通过以下程序我们可以看到,对字符串做\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 );
    char *lpsz= new char[iLen+1];
    int i= wcstombs( lpsz, wsm[1].str().c_str(), iLen );
    lpsz[iLen] = '\0';

    string sToMatch(lpsz);
    delete []lpsz;
    cout << "匹配结果:" << sToMatch << endl;
}

void main()
{
    string sToMatch("数超限");
    MatchWords( sToMatch );
    sToMatch = "节点数目超限";
    MatchWords( sToMatch );

    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;
    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';

std::string sToMatch(lpsz);

delete[] lpsz;

原文地址:https://www.cnblogs.com/cy163/p/1716695.html