最近工作需要在xml node的节点中加入一些Unicode编码的某些国家的字符,比如“Alte Poststraße”,最后把xml node变成字符串传送给HMI。
为了图方便(想象中的方便),心想这样一个字符串用项目中所用的TinyXML生成node然后转为string的方案较为合理。然检查TinyXML的API发现,构造函数如TiXmlText的参数是char*,所以不能将Unicode格式的字符串传给它,需要先将宽字符转成多字节后传递。
上述的“Alte Poststraße”有个“ß”无法用ascii表示,只能用Unicode或UTF-8。看看调用项目内Unicode转UTF-8函数是怎么个情形:
static int UnicodeToUtf8(wchar_t wchar, char utf8[], const int nLen)
{
...
if (wchar < 0x80)//128,和ascii相同,1byte unicode,1byte utf8
{ //
//length = 1;
utf8[len++] = (char)wchar;//直接赋值
}
else if(wchar < 0x800)//小于12位,1000 0000 0000,2byte unicode,2byte utf8
{
//length = 2;
if (len + 1 >= nLen)
return -1;
utf8[len++] = 0xc0 | ( wchar >> 6 );//7~11位 | 110xxxxx
utf8[len++] = 0x80 | ( wchar & 0x3f );//1~6位 | 10xxxxxx
}
else if(wchar < 0x10000 )//小于17位,1 0000 0000 0000 0000,2byte unicode,3byte utf8
{
//length = 3;
if (len + 2 >= nLen)
return -1;
utf8[len++] = 0xe0 | ( wchar >> 12 );//13~16位 | 1110xxxx
utf8[len++] = 0x80 | ( (wchar >> 6) & 0x3f );//7~11位 | 10xxxxxx
utf8[len++] = 0x80 | ( wchar & 0x3f );//1~6位 | 10xxxxxx
}
else if( wchar < 0x200000 )//小于22位,10 0000 0000 0000 0000 0000,3byte unicode,4byte utf8
{
//length = 4;
if (len + 3 >= nLen)
return -1;
utf8[len++] = 0xf0 | ( (int)wchar >> 18 );//17~21位 | 11110xxx
utf8[len++] = 0x80 | ( (wchar >> 12) & 0x3f );//13~16位 | 1110xxxx
utf8[len++] = 0x80 | ( (wchar >> 6) & 0x3f );//1~6位 | 10xxxxxx
utf8[len++] = 0x80 | ( wchar & 0x3f );//1~6位 | 10xxxxxx
}
return len;
}
首先要了解下Unicode和UTF-8关系:
Unicode是计算机科学领域里的一项业界标准,是几乎包括世界上所以字符集的编码方案,为每种语言中的每个字符设定了统一并且唯一的二进制编码。UTF,UnicodeTransformation Format的缩写,即Unicode转换格式。UTF-8是UNICODE的一种变长字符编码,用1到6个字节编码UNICODE字符。
如果UNICODE字符由2个字节表示,则编码成UTF-8很可能需要3个字节,如果UNICODE字符由4个字节表示,则编码成UTF-8可能需要6个字节。用4个或6个字节去编码一个UNICODE字符的情况其实甚少。
UTF-8编码规则:
如果只有一个字节则其最高二进制位为0;如果是多字节,其第一个字节从最高位开始,连续的二进制位值为1的个数决定了其编码的位数,其余各字节均以10开头。UTF-8最多可用到6个字节。
如表:
1字节 0xxxxxxx
2字节 110xxxxx 10xxxxxx
3字节 1110xxxx 10xxxxxx 10xxxxxx
4字节 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
5字节 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
6字节 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
故UTF-8中可以用来表示字符编码的实际位数最多有31位,即表中x所表示的位。除去那些控制位(每字节开头的10等),这些x表示的位与UNICODE编码是一一对应的,位高低顺序也相同。
现在看代码,“ß”的Unicode是223,本身有8位,因此对于UTF-8而言1字节不够表示只能用2字节,落在表中第2行。取223的高6位并和110xxxxx相或,得C3;取223的低6位并和10xxxxxx相或,得9F。因此最后UTF-8多字节内容为C3 9F。
现在问题来了,存在xmlnode里面的内容是C3 9F,如果xml以UTF-8编码,存取后读出内容并以宽字符(如wstring)存储的话,是可以还原“ß”的,然而我用了窄字符(string)去存多字节的C3 9F,结果在代码内被解析为“ß”(Ã的Unicode是C3,Ÿ实际上是乱码,因为没有Unicode 9F的特定对应字符)。
至于解决方法,我直接用wstring字符串拼接法凑出了这个xmlnode。其实也可以提取xml的多个字节的UTF-8并按上述规则重新转为UNICODE(亲测有效),当然这是后话了。
顺便提一句,发现不同软件模块之间字符传输的编码格式多为UTF-8(xml编码,以及发送给HMI之前还要被统一转为UTF-8,实际上我是多此一举),而IDE的显示或者wstring内部实现应该为Unicode(用wstring和Tchar表示较为方便,不需要考虑有多少个byte)。