SAS岩论|在SAS中如何解决中文乱码问题(2)-干货篇

 

首先,GBK编码方案中,总体的编码是有范围的,从8140~FEFE。其中首字节在81~FE之间,尾字节在40~FE之间,剔除xx7F一条线;总计23940个码位,共收入21886个汉字和图形符号,其中汉字21003个,图形符号883个。...



上一期文章对中文乱码产生的情况进行了原因剖析,也提供了从底层解决问题的思路。本篇文章就上期文章的思路进行代码实现。上期文章传送门:

SAS岩论|在SAS中如何解决中文乱码问题(1)

整体解决思路
首先,GBK编码方案中,总体的编码是有范围的,从8140~FEFE。其中首字节在81~FE之间,尾字节在40~FE之间,剔除xx7F一条线;总计23940个码位,共收入21886个汉字和图形符号,其中汉字21003个,图形符号883个。

其次,ASCII编码的范围是从00~7F,共计128个码位,收入大小写英文字母、数字、符号、及特殊控制字符。其中可显示的编码范围为20~7E;

因此整体的解决过程为:

  1. 将字符串转化为16进制编码;
  2. 首先提取前2位编码,判断这两位编码是否大于等于81,进而决定是否是中文;
  3. 如果小于81则认为是英文字符,直接将其保存;如果大于等于81,则是中文的开始,进行标记,保存到临时的变量,等待第二个字节信息;
  4. 继续下次循环提取第二个字节编码(每个字节2位编码),通过标记判断当前编码是否属于中文第二个字节;如果不是,继续第三步的判断;如果是,则判断该字节编码是否大于40,且不等于7F。如果满足则说明中文没问题,则追加至临时变量,然后直接输出;如果不满足,则说明上个字节是中文断码,忽略上个字节信息;然后继续第3步判断,继续流程,直至全部编码处理完毕。
没有必要而有意义的过程
要对十六进制编码进行大小的比较,首先需要将其转化为十进制。于是我就直接敲击键盘写出了十六进制转十进制的macro。但是后来,才想来直接用input函数就可完成。但是,这个不必要的过程我觉得还是很有意义分享一下。主要有两点:

  • SAS Macro的强大及方便;
  • 在Data步中实时调用执行宏;
为了不影响整个过程,这部分内容在文章最后进行介绍。
示例数据准备
为了演示解决代码,首先利用SAS代码来人为制造一份数据:

/*

“我是1个SAS用户”,其GBK编码为:CED2CAC731B8F6534153D3C3BBA7

其中,我:CED2;是:CAC7;1:31;个:B8F6;S:53;A:41;S:53;用:D3C3;户:BBA7

基于此生成4条样例数据:

  • 第一条为完整的;
  • 第二条去掉“户”的最后两个编码;
  • 第三条去掉“是”的最后两个编码;
  • 第四条去掉“我”的最后两个编码;
*/

data testdata;

length code $ 30 str $ 15;

input code $;

str=input(code,$hex30.);

cards;

CED2CAC731B8F6534153D3C3BBA7

CED2CAC731B8F6534153D3C3BB

CED2CA31B8F6534153D3C3BBA7

CECAC731B8F6534153D3C3BBA7

;

run;

首先我们对编码进行人工的判断:

  • 第一条数据没有问题,就是:“我是1个SAS用户”;
  • 第二条数据末尾,存在BB这个断码(乱码),因此无法显示,需要处理。处理的方法就是删除这个断码BB,从而结果变成“我是1个SAS用”;
  • 第三条数据,原本“是1”对应编码CAC731变成CA31;由于CA31没有对应的汉字(汉字的第二字节编码范围是40~FE),因此此处会形成一个乱码,无法显示,需要处理。处理的方法就是删除CA这个断码删除,31继续显示为数字“1”,因此处理后结果为“我1个SAS用户”;
  • 第四条数据,原本“我是1”对应编码CED2CAC731,变成CECAC731;碰巧CECA对应汉字“问”,而C731跟上面一样是一个乱码,因此也无法显示,需要处理。由于CECA碰巧形成一个新的汉字,不会处理,只会将C7这个断码删除,31继续显示为数字“1”,因此处理后结果为“问1个SAS用户”;
其中特别说明,第四条数据中,由于“我是”对应的编码为CED2CAC7,将D2人为剔除,CECA组合成一个新的汉字“问”。这种情况是无法识别并处理的,只有不符合规则的情况例如C731这种不属于汉字编码范围内的编码才能识别并加以处理。
上干货
为了排版效果,只能截图了:



结果如下所示:



好了,关于中文乱码的处理程序到此就结束了,如果各位有更好的意见,欢迎评论留言。(想要代码请在SAS中文论坛微信群获取)
补上之前的宏程序


废话不多说,直接上代码:

%macro HexTranscode(h);

%if%eval(&h ge 0 and &h le 9) %then &h;

%else%if%upcase(&h)=A %then10;

%else%if%upcase(&h)=B %then11;

%else%if%upcase(&h)=C %then12;

%else%if%upcase(&h)=D %then13;

%else%if%upcase(&h)=E %then14;

%else%if%upcase(&h)=F %then15;

%mend;

%macro HexToDecimal(hex);

%let length=%length(&hex);

%do i=1%to &length;

%let h&i=%HexTranscode(%substr(&hex,&i,1));

%end;

%let result=0;

%do i=1%to &length;

%let result=%eval(&result +&&h&i*(16**%eval(&length-&i)));

%end;

&result

%mend;

%put %HexToDecimal(81);

%put %HexToDecimal(8140);

日志结果:



怎么样?用sas macro完成十六进制到十进制的转换是不是很完美?还不会macro的SAS小白,速度学起!我相信用其他语言来完成这个转换过程估计没这么完美吧(不能用内置的函数转换)?如果有欢迎留言区上代码!我定会好好学习!

另外一个点是如何在Data步中实时的获取宏的结果。学习过macro的小伙伴都知道,如果直接在data步中含有直接的macro调用,macro是先于data步解析执行的。所以等data步开始执行的时候,macro的结果已经定了。

所以如何在Data步中来实时解析调用该宏来得到转换结果,各位小伙伴可以想一想,下期分享再说。

1月原创好文回顾,请点击阅读:

SAS岩论|在SAS中如何解决中文乱码问题(1)

SAS岩论|SAS EG中如何显示Data步处理进度


    关注 SAS中文论坛


微信扫一扫关注公众号

0 个评论

要回复文章请先登录注册