搜索
查看: 1852|回复: 8

unicode编码和字符相互转换

[复制链接]

2

主题

17

帖子

306

积分

二级会员

Rank: 3Rank: 3

积分
306
发表于 2017-11-18 16:16:02 | 显示全部楼层 |阅读模式
本帖最后由 thXnder 于 2017-11-18 16:22 编辑

有时候,我们想根据给定的unicode编码(可能大于0xFFFF),得到相应的字符,aardio中似乎没有直接转换的函数,于是我利用web.script库并参考网络文章写了一个:
  1. var unicode2chr = function(unicode) begin //把unicode编码(形如0x2B81C)转换成字符(形如“你”)
  2.         import web.script;
  3.         if(unicode <= 0xFFFF) return string.unescape(string.replace(tostring(unicode,16), "@0x", "\u"));
  4.         unicode -= 0x10000;  //最长20bit
  5.         var high = (unicode >> 10) + 0xD800;
  6.         var low = (unicode & 2_1111111111) + 0xDC00; //此处的按位与相当于(unicode << 22) >> 22;
  7.         return web.script().run("unescape", string.replace(tostring(high,16)++tostring(low,16), "@0x", "%u"));
  8. end;
  9. import console; console.log(unicode2chr(0x4F60));
复制代码
参考文章:
[1] https://www.cnblogs.com/dragon2012/p/5020259.html
[2] http://blog.csdn.net/thl789/article/details/7506133

注意,如果你的unicode编码大于0xFFFF,例如字(其unicode编码为0x2B81C,参见http://yedict.com/zscontent.asp?uni=2B81C),上面的函数也能把它转换成字符,但控制台的点阵字体是无法显示它的,可以把它输出到文件或者用文本框控件查看(当然,需要先安装大字符集的字体,如开心宋体或者天珩字体后重启才能看到)。
.
把上面的计算过程反过来,就可以实现查询某个字符对应的unicode编码
  1. var chr2unicode = function(chr) begin //逆运算
  2.         import web.script;
  3.         var utfcode = string.split(web.script().run("escape", chr), '<%u>');
  4.         if(#utfcode==2) return tonumber("0x" ++ utfcode[2]);
  5.         var high = tonumber("0x" ++ utfcode[2]) - 0xD800;
  6.         var low = tonumber("0x" ++ utfcode[3]) - 0xDC00;
  7.         return (high << 10) + low + 0x10000;
  8. end;
  9. import console; console.log(tostring(chr2unicode("你"),16))
复制代码

不知道是否有理解不当之处,故在此分享,希望与老师、同学们交流。

附:unicode编码划分给汉字用的空间大致如下:
空间名称unicode编码范围
基本区4E00~9FA5
基本区补充9FA6~9FCF
扩展A区3400~4DB5
扩展B区20000~2A6D6
扩展C区2A700~2B734
扩展D区2B740~2B81D
扩展E区2B820~2CEA1
扩展F区2CEB0~2EBE0
康熙部首2F00~2FD5
部首扩展2E80~2EF3
兼容汉字F900~FAD9
兼容扩展2F800~2FA1D
PUA(GBK)部件E815~E86F
部件扩展E400~E5E8
PUA增补E600~E6CF
汉字笔画31C0~31E3
汉语注音3105~3120
注音扩展31A0~31BA


回复

使用道具 举报

185

主题

2541

帖子

1万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
13974
发表于 2017-11-18 17:49:14 | 显示全部楼层
感谢分享代码,但是:

你的代码里调用js返回的是%uD86E%uDC1C,这跟aardio中的 \uD86E\uDC1C 不是一回事吗?!
你用Javascript得到了2个字节的Unicode代码,然后再用aardio把他改成4个字节的,这是什么情况?!
在aardio中你用 string.escape('那个字符'u,true,true) 的结果不是一样吗?!

回复

使用道具 举报

185

主题

2541

帖子

1万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
13974
发表于 2017-11-18 17:53:08 | 显示全部楼层
aardio的默认编码是变长的UTF8,所以支持变长Unicode是没问题的。

至于UTF16,虽然理论上他也是变长的,但实际上我们有了UTF8,再去搞一个变长的UTF16是无意义的,所以我们假定他是定长的2个字节,这个原因很简单,因为从实用的角度来说,谁也不愿意在编程时加上大量的代码、或者浪费大量的存储空间去兼容一个99%的人根本用不到的几个罕见的字符, 在其他编程语言中也基本都是这样处理的。

至于你上面的代码,直接取字节码来运算就可以:
var charCode = function(chr){
   
var chr = raw.convert({BYTE u[] = chr},{ WORD hi;WORD low }) ;
   
return 0x10000 + (chr.hi - 0xD800 << 10) + chr.low - 0xDC00;
}
回复

使用道具 举报

2

主题

17

帖子

306

积分

二级会员

Rank: 3Rank: 3

积分
306
 楼主| 发表于 2017-11-18 19:39:03 | 显示全部楼层
Jacen.He 发表于 2017-11-18 17:49
感谢分享代码,但是:

你的代码里调用js返回的是%uD86E%uDC1C,这跟aardio中的 %uD86E%uDC1C 不是一回事 ...

感谢版主回复,在逆运算时的确可以直接用string.escape(chr, true, true)函数起到和web.script().run("escape", chr)函数一样的效果,因此上面的逆运算代码可以不用web.script库从而简化成:

  1. var chr2unicode = function(chr) begin //逆运算
  2.         var utfcode = string.split(string.escape(chr,true,true), '<\\u>');
  3.         if(#utfcode==2) return tonumber("0x" ++ utfcode[2]);
  4.         var high = tonumber("0x" ++ utfcode[2]) - 0xD800;
  5.         var low = tonumber("0x" ++ utfcode[3]) - 0xDC00;
  6.         return (high << 10) + low + 0x10000;
  7. end;
复制代码


不过在正向运算时,string.unescape('\uD86E\uDC1C')似乎就无法正确转换了,所以正向运算还是要借助web.script库
回复

使用道具 举报

2

主题

17

帖子

306

积分

二级会员

Rank: 3Rank: 3

积分
306
 楼主| 发表于 2017-11-18 19:40:13 | 显示全部楼层
Jacen.He 发表于 2017-11-18 17:49
感谢分享代码,但是:

你的代码里调用js返回的是%uD86E%uDC1C,这跟aardio中的 %uD86E%uDC1C 不是一回事 ...

版主可能误会我的意思了,我想实现的是“「Unicode编码」和「字符」的相互转换” ,注意这里的「Unicode编码」不是指utf8也不是utf16这些具体的方式,而是指像下表中左侧的数值。

Unicode编码(十六进制) UTF8字节流(二进制)
0-7F 0xxxxxxx
80-7FF 110xxxxx 10xxxxxx
800-FFFF 1110xxxx 10xxxxxx 10xxxxxx
10000-1FFFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

即,我的目标是支持“&#178204;”这样的字和其Unicode码0x2B81C(注意,这个码大于0x10000)的相互转换(可以用http://yedict.com/zscontent.asp?uni=XXX查询某个Unicode编码对应的汉字字符,XXX是Unicode码)。虽然这是个很小众的需求,但在处理某些汉字数据的时候,还是能用到的。
.
判断反向转换(即字->码)是否成功很简单,只要chr2unicode("你")等于0x4F60,并且chr2unicode("&#178204;")等于0x2B81C,基本可以判断该函数达成目标。
遗憾的是,版主给的charCode("你")函数验证失败,两个字的结果都是负数,我不太懂编程, 暂时还不知道怎么修改:(
.
判断正向转换是否成功就比较麻烦了,首先要安装字体(比如开心宋体),然后把unicode2chr(0x2B81C)的结果用string.save保存成utf-8文件,再用UltraEdit之类的文本编辑器打开,看看里面是否显示"&#178204;"字。


回复

使用道具 举报

185

主题

2541

帖子

1万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
13974
发表于 2017-11-19 23:10:52 | 显示全部楼层
thXnder 发表于 2017-11-18 19:40
版主可能误会我的意思了,我想实现的是“「Unicode编码」和「字符」的相互转换” ,注意这里的「Unicode ...



不去了解UTF16为什么把4个字节实际的拆分为2个字符,怎么去提取转换得到他的码值呢?!
我前面简单的提了几句UTF16为什么要这样实现,包含你自己贴的链接,写的代码都是在讲这个,怎么就误会到你了呢?!到底是误会了什么东西?!

你发现 charCode("你") 就用不了,是因为我只是在告诉你关键的难点 - 演示了你所说的4字节Unicode码值的转换,而2字节UTF16在 aardio里本来就直接可以用下标返回Unicode码值。
-----------------------------------------------------------------

你在JS里取到的%uD86E%uDC1C,实际上也并不是你所要的Unicode编码,这其实算是用了两个节符在耍流氓!
aardio的确不支持类似JS里的'\uD86E\uDC1C'这种用两个节符耍流氓的写法,但aardio里也可以写 string.fromUnicode('\x6E\xD8\x1C\xDC') ,直接写4个字节就行了。

这个写法,aardio与js只是写法不同而已,
同样的都不支持UTF16 4字节直接取码值,所以我说并没有必要调用JS来转换一下。

因为你关键的代码只是数值的按位运算( 例如4字节转换为一个32位数值,或者反之)
给你一个完整的代码:
import win;
   
//取Uniocde字符的编码值(2字节、或4字节UTF16编码)
var charCode = function(chr){
    chr = string.toUnicode(chr);
//你调用JS是aardio自动做了这个转换
    if( chr[1] < 0xD800 || chr[1] > 0xDFFF ) return  chr[1]; //2字节直接取字节码
    return 0x10000 + (chr[1] - 0xD800 << 10) + chr[2]- 0xDC00;
}

//Unicode编码转换为UTF16字符串(2字节、或4字节UTF16编码)
var fromCharCode = function(chr){
   
var ustr = raw.buffer({
        WORD bytes[] = chr<0x10000 ?  {chr} : { (chr-0x10000 >> 10)  + 0xD800;(chr & 2#1111111111) + 0xDC00 };
    })
   
return raw.str( ustr,true);
}

var ustr  = fromCharCode(0x2B81C);

win.msgbox( ustr )
win.msgbox( charCode(ustr)  );
win.msgbox( charCode(
"你")  );


回复

使用道具 举报

2

主题

17

帖子

306

积分

二级会员

Rank: 3Rank: 3

积分
306
 楼主| 发表于 2017-11-20 04:11:15 | 显示全部楼层
Jacen.He 发表于 2017-11-19 23:10
不去了解UTF16为什么把4个字节实际的拆分为2个字符,怎么去提取转换得到他的码值呢?!
我前面简单的 ...

我发到论坛时已感觉自己理解不当需要指点,4L、5L的回复绝无抱怨之意,但是我没有认真思考2L、3L的内容,而且用词不当过于随意了,以为是老大误会我的意思,其实是我根本没有理解
我发现自己连UTF-8和UTF-16都没有分清楚,之前您给的charCode函数针对的是大于0x10000的utf16编码字符,我直接调用charCode("&#178204;")的话传进去的是aardio默认的utf8编码字符,结果当然不对了.
再看到老大刚回复的代码,感觉特别有启发!尤其是直接操作字节码的部分,之前一直不明白,现在有了这个例子清晰太多了,谢谢老大!!可惜现在时候不早,我改天再好好整理一下
回复

使用道具 举报

185

主题

2541

帖子

1万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
13974
发表于 2017-11-22 23:13:52 | 显示全部楼层
thXnder 发表于 2017-11-20 04:11
我发到论坛时已感觉自己理解不当需要指点,4L、5L的回复绝无抱怨之意,但是我没有认真思考2L、3L的内容,而 ...

昨天改进了一下aardio,加了一些功能,
例如可以用 '\U02b81c','\uDC1C\u4F60ff' 这种转义了,
另外加了 ustring.pack, ustring.unpack, ustring.charCodeAt 这些函数用来支持4字节Unicode,但是这些字符估计也难以通用,很多系统没有支持的字体显示不了。

所以,现在上面的代码可以简化为这样:
import win;
import ustring;
  
win.msgbox( ustring.pack(0x2B81C) )
win.msgbox( ustring.unpack(
'\U02b81c')  );



回复

使用道具 举报

2

主题

17

帖子

306

积分

二级会员

Rank: 3Rank: 3

积分
306
 楼主| 发表于 2017-11-26 18:17:17 | 显示全部楼层
Jacen.He 发表于 2017-11-22 23:13
昨天改进了一下aardio,加了一些功能,
例如可以用 '%u02b81c','%uDC1C%u4F60ff' 这种转义了,
另外加 ...

这。。强大却简洁得超乎想象,版主辛苦了
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册会员

本版积分规则

手机版|未经许可严禁引用或转载本站文章|站长邮箱|aardio.com|aardio官方社区 ( 皖ICP备09012014号 )

GMT+8, 2018-12-11 20:00 , Processed in 0.062507 second(s), 22 queries .

Powered by Discuz! X3.4

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表