先说几个场景:
- 使用文件字符流打开一个文本文档,但是我不确定是以UTF8编码的还是GB18030,所以就无法准确设置TranslateTable,就导致了中文乱码问题。
- 有一个文件下载的csp,其中文件名参数可能是中文,如果在一个UTF8编码的界面直接调用时,后台取到的文件名就会是乱码。
- 接收到字节流后需要转成字符流读取内容,但是无法确定编码格式,就无法准确的转成字符。
以上几个场景虽然大多都可以提前做好约定解决,但是可能有历史原因或者种种情况,需要我们自己能够解决,于是就有了下面的故事。
基础
首先我方系统使用GB18030编码,然后碰到的情况大多都是对方可能是UTF8编码,所以主要来解决识别字节流是不是UTF8编码的。
然后查了一个UTF8编码格式
- 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
有了UTF8编码格式,然后逐字节进行判断,看整个字节序列是否完美符合UTF8编码,所以先实现了方法IfMatchUTF8Bytes
/// 是否是符合UTF8的字节序列
/// bytes 字节串或字节流
/// maxLen 最大验证长度 超过此长度的不再验证
/// Output Count 符合1-6字节编码标准的字符数 $lb(f1,f2,f3,f4,f5,f6)
/// 返回值 1符合 0不符合
ClassMethod IfMatchUTF8Bytes(bytes, maxLen = 30000, Output Count)
{
///code
}
基于IfMatchUTF8Bytes
方法,然后又实现了一个方法GuessUTF8Bytes
,此方法又做了一些其它的判断:如果前三字节为UTF8BOM则直接认定为UTF8,如果校验结果中只有1、2字节的字符,而3-6字节字符没有,则认定为不是UTF8编码。
/// 猜测是否是UTF8字节序列
/// bytes 字节串或字节流
/// 返回值 1符合 0不符合
ClassMethod GuessUTF8Bytes(bytes)
{
///code
}
场景1
基于以上,我们就可以实现一个方法打开某文件获得字符流,自动判断编码并设置TranslateTable为相应编码了。
/// 打开某文件获得字符流(自动判断编码并设置TranslateTable为相应编码 目前只支持UTF8)
/// s fileSteam=##class(BSP.SYS.COM.Charset).OpenFileCharacterStream(fullName)
ClassMethod OpenFileCharacterStream(fullName = "", autocharset) As %FileCharacterStream
{
s fileSteam=##class(%FileCharacterStream).%New()
s sc=fileSteam.LinkToFile(fullName)
if $$$ISERR(sc) {
q ""
}
s oldTranslateTable=fileSteam.TranslateTable
s fileSteam.TranslateTable="RAW" //判断字节 需要先将TranslateTable设置成RAW
if ..GuessUTF8Bytes(fileSteam) {
d fileSteam.Rewind()
s fileSteam.TranslateTable="UTF8"
}else{
d fileSteam.Rewind()
s fileSteam.TranslateTable=oldTranslateTable
}
q fileSteam
}
这样我们就可以随便读取本地的文本文件了,也不会乱码了,解决场景1。
场景2
如何判断一个请求它的参数是以UTF8编码的还是以GB18030呢,通过测试发现我们可以通过GetCgiEnv("QUERY_STRING")
获取到请求的参数的,且测试发现不同浏览器设置可能不同,有的为URL编码后的,有的则只是按UTF8或GB18030编码后的字节序列。对于URL编码后的可以先使用$zconvert(url,"I","URL")
解码获得字节序列再进行判断,故而实现了方法IfMatchUTF8EscapedURL
与GuessUTF8EscapedURL
。
/// 是否是符合UTF8编码并进行URL编码的字符串
/// url 字符传
/// maxLen 最大验证长度 超过此长度的不再验证
/// Output Count 符合1-6字节编码标准的字符数 $lb(f1,f2,f3,f4,f5,f6)
/// 返回值 1符合 0不符合
ClassMethod IfMatchUTF8EscapedURL(url, maxLen = 30000, Output Count)
{
s bytes=$zconvert(url,"I","URL")
s ret=..IfMatchUTF8Bytes(bytes,maxLen,.Count)
q ret
}
/// 猜测是符合UTF8编码并进行URL编码的字符串
/// url 字符传
/// 返回值 1符合 0符合
ClassMethod GuessUTF8EscapedURL(url)
{
///code
}
基于以上就可以实现一个方法判断请求调用方是不是UTF8编码的了。
/// req %CSP.Request对象
/// 返回值 1符合 0不符合
ClassMethod GuessUTF8Request(req As %CSP.Request)
{
///code
}
另外由于编码格式不一致,通过%requet.Data拿到的数据就会出现乱码,此时可以通过将自己解析QUERY_STRING来获取数据,于是有了ParseRequestData
/// 解析请求数据 (自动判断编码 ,目前只支持UTF8)
/// 目前只实现了从QUERY_STRING解析 在请求体的暂未找到原始数据
/// req %CSP.Request对象
/// data ByRef 解析出来的数据
ClassMethod ParseRequestData(req As %CSP.Request, ByRef data)
{
///code
}
似乎这样就解决了场景二
场景3
有了上面的基础,似乎场景3自然而然水到渠成了。字节流转字符流首先想到的是读取字节,然后使用$zcvt(bytes,"I",charset)
转换,但是这种需要注意不要将一个字符的多个字节分成了两段。此处是利用了%FileCharacterStream
做了一次中转,使用其TranslateTable进行转换,不知是否有其它方法支持
/// 将字节串转换为字符串(自动猜编码,目前只支持UTF8)
/// 将字节流转换为字符流(自动猜编码,目前只支持UTF8)
/// bytes 字节串或字节流
/// 返回值 字符串或字符流
/// w ##class(BSP.SYS.COM.Charset).Bytes2Chars($zcvt("测试","O","GB18030") )
ClassMethod Bytes2Chars(bytes)
{
///code
}
代码下载
总结
一个小玩具,分享出来大家看看