登录 用户中心() [退出] 后台管理 注册
   
您的位置: 首页 >> CLQ工作室开源代码 >> 主题: [推荐][utf8][delphi]手动分隔 utf8 各个字符及可能的使用场景     [回主站]     [分站链接]
标题
[推荐][utf8][delphi]手动分隔 utf8 各个字符及可能的使用场景
clq
浏览(288) + 2020-07-03 22:20:06 发表 编辑

关键字:

[推荐][utf8][delphi]手动分隔 utf8 各个字符及可能的使用场景

现在的 utf8 解码模块基本上都有一个致命的问题:当字符串中有一个不合法的 utf8 编码就会整个字符串解码失败。
当然有些库可以做忽略操作,不过最好的方式是能自己分割各个 utf8 字符,然后自己转换。

因为 d7 在这种情况下是会得到一个空白字符串,因此要有一个自己的分割是必须的。我的代码如下。其实大家可以很容易地翻译成其他语言。

unit utf8_decode;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, IdBaseComponent, IdComponent, IdTCPConnection,
IdTCPClient, IdHTTP;

//手动解码 //delphi7 是解码不了笑脸符的
function Utf8Decode_manual(s:string):string;


implementation

//参考文件夹 utf8_decode

(*
http://www.cnblogs.com/1175429393wljblog/p/4562736.html
JS计算字符串所占字节数

最近项目有个需求要用js计算一串字符串写入到localStorage里所占的内存,众所周知的,js是使用Unicode编码的。而Unicode的实现有N种,其中用的最多的就是UTF-8和UTF-16。因此本文只对这两种编码进行讨论。
下面这个定义摘自维基百科(http://zh.wikipedia.org/zh-cn/UTF-8),做了部分删减。
原文来自:http://www.alloyteam.com/2013/12/js-calculate-the-number-of-bytes-occupied-by-a-string/
UTF-8(8-bit Unicode Transformation Format)是一种针对Unicode的可变长度字符编码,可以表示Unicode标准中的任何字符,且其编码中的第一个字节仍与ASCII相容,使用一至四个字节为每个字符编码

其编码规则如下:

字符代码在
000000 – 00007F之间的,用一个字节编码;
000080 – 0007FF之间的字符用两个字节;
000800 – 00D7FF 和 00E000 – 00FFFF之间的用三个字节,注: Unicode在范围 D800-DFFF 中不存在任何字符;
010000 – 10FFFF之间的用4个字节。


而UTF-16 则是定长的字符编码,大部分字符使用两个字节编码,字符代码超出 65535 的使用四个字节,如下:

000000 – 00FFFF 两个字节;
010000 – 10FFFF 四个字节。

一开始认为既然页面用的是UTF-8编码,那么存入localStorage的字符串,应该也是用UTF-8编码的。
但后来测试发现,明明计算出的size是不到5MB,存入localStorage却抛异常了。想了想,页面的编码是可以改的。
如果localStorage按照页面的编码存字符串, 不就乱套了?浏览器应该都是使用UTF-16编码的。
用UTF-16编码计算出5MB的字符串,果然顺利写进去了。超过则失败了。
好了,附上代码实现。计算规则就是上面写的,为了计算速度,把两个for循环分开写了。

/**
* 计算字符串所占的内存字节数,默认使用UTF-8的编码方式计算,也可制定为UTF-16
* UTF-8 是一种可变长度的 Unicode 编码格式,使用一至四个字节为每个字符编码
*
* 000000 - 00007F(128个代码) 0zzzzzzz(00-7F) 一个字节
* 000080 - 0007FF(1920个代码) 110yyyyy(C0-DF) 10zzzzzz(80-BF) 两个字节
* 000800 - 00D7FF
00E000 - 00FFFF(61440个代码) 1110xxxx(E0-EF) 10yyyyyy 10zzzzzz 三个字节
* 010000 - 10FFFF(1048576个代码) 11110www(F0-F7) 10xxxxxx 10yyyyyy 10zzzzzz 四个字节
*
* 注: Unicode在范围 D800-DFFF 中不存在任何字符
*
{@link onclick="javascript:pageTracker._trackPageview('/outgoing/zh.wikipedia.org/wiki/UTF-8');"

href="http://zh.wikipedia.org/wiki/UTF-8">http://zh.wikipedia.org/wiki/UTF-8}
*
* UTF-16 大部分使用两个字节编码,编码超出 65535 的使用四个字节
* 000000 - 00FFFF 两个字节
* 010000 - 10FFFF 四个字节
*
*
{@link onclick="javascript:pageTracker._trackPageview('/outgoing/zh.wikipedia.org/wiki/UTF-16');"

href="http://zh.wikipedia.org/wiki/UTF-16">http://zh.wikipedia.org/wiki/UTF-16}
* @param {String} str
* @param {String} charset utf-8, utf-16
* @return {Number}
*/
var sizeof = function(str, charset){
var total = 0,
charCode,
i,
len;
charset = charset ? charset.toLowerCase() : '';
if(charset === 'utf-16' || charset === 'utf16'){
for(i = 0, len = str.length; i < len; i++){
charCode = str.charCodeAt(i);
if(charCode <= 0xffff){
total += 2;
}else{
total += 4;
}
}
}else{
for(i = 0, len = str.length; i < len; i++){
charCode = str.charCodeAt(i);
if(charCode <= 0x007f) {
total += 1;
}else if(charCode <= 0x07ff){
total += 2;
}else if(charCode <= 0xffff){
total += 3;
}else{
total += 4;
}
}
}
return total;
}
龙腾一族至尊龙骑
*)

function decode_utf8_to_single(s:AnsiString):string;
var
i:Integer;
single:AnsiString;
b,b1,b2,b3,b4:Byte;
index_in_single:Integer; //当前计算到了 single 单个 utf8 字符中的第几个字节了
begin
Result := '';
single := '';
index_in_single := 0;
s := AnsiToUtf8(s);
for i := 1 to Length(s) do
begin
b := Byte(s[i]);

//其实 utf8 是根据第一个字节来推断一个字符占用几个字节的
//小于 0x80 bin(1000 0000) 为单字节
//小于 0xe0 bin(1110 0000) 为双字节
//小于 0xf0 bin(1111 0000) 为三字节
//小于 0xf8 bin(1111 1000) 为4字节

//但它们对第二位及后面的字节也并不是全部占完的,似乎是为了方便在任意位置分割字符串


if index_in_single = 0 then
begin
b1 := b;
if b1 < $80 then //if b1 <= $007f then //0x007f //单字节字符 //0zzzzzzz(00-7F)//7f的二进制是 01111111 第一个字节第一位为 0 的都是单字节字符
begin
single := AnsiChar(b1);
Result := Result + single + #13#10;
index_in_single := 0; //重置位置
Continue;
end;

end;

if index_in_single = 1 then
begin
b2 := b;
if b1 < $e0 then //0x07ff //单字节字符 //0x07ff的二进制是 0111 11111111 //这个算法很奇怪,并没有用完全部的第二个字节
//它的意思是说第一个字节全部用完都为1的情况下用第2个字节的一半表示双字节的字符
begin
single := AnsiChar(b1) + AnsiChar(b2);
single := Utf8ToAnsi(single) + '| - 占用 2 字节'; //这个是为了还原为 gbk 而已
Result := Result + single + #13#10;
index_in_single := 0; //重置位置
Continue;
end;

end;

if index_in_single = 2 then
begin
b3 := b;
if b1 < $f0 then //0x07ff //单字节字符 //0x07ff的二进制是 0111 11111111 //这个算法很奇怪,并没有用完全部的第二个字节
//它的意思是说第一个字节全部用完都为1的情况下用第2个字节的一半表示双字节的字符
begin
single := AnsiChar(b1) + AnsiChar(b2) + AnsiChar(b3);
single := Utf8ToAnsi(single) + '| - 占用 3 字节'; //这个是为了还原为 gbk 而已
Result := Result + single + #13#10;
index_in_single := 0; //重置位置
Continue;
end;

end;

if index_in_single = 3 then
begin
b4 := b;
if b1 < $f8 then //目前来说其实不用判断也可以,除非以后扩展到超过 4 个字节了
//
begin
single := AnsiChar(b1) + AnsiChar(b2) + AnsiChar(b3) + AnsiChar(b4);
single := Utf8ToAnsi(single) + '| - 占用 3 字节'; //这个是为了还原为 gbk 而已
Result := Result + single + #13#10;
index_in_single := 0; //重置位置
Continue;
end;

end;


index_in_single := index_in_single + 1;
end;

end;

function decode_utf8_to_single_2020(s:AnsiString):string;
var
i:Integer;
single:AnsiString;
b,b1,b2,b3,b4:Byte;
index_in_single:Integer; //当前计算到了 single 单个 utf8 字符中的第几个字节了
begin
Result := '';
single := '';
index_in_single := 0;
////s := AnsiToUtf8(s); //输入就是 utf8 不要再转换了
for i := 1 to Length(s) do
begin
b := Byte(s[i]);

//其实 utf8 是根据第一个字节来推断一个字符占用几个字节的
//小于 0x80 bin(1000 0000) 为单字节
//小于 0xe0 bin(1110 0000) 为双字节
//小于 0xf0 bin(1111 0000) 为三字节
//小于 0xf8 bin(1111 1000) 为4字节

//但它们对第二位及后面的字节也并不是全部占完的,似乎是为了方便在任意位置分割字符串


if index_in_single = 0 then
begin
b1 := b;
if b1 < $80 then //if b1 <= $007f then //0x007f //单字节字符 //0zzzzzzz(00-7F)//7f的二进制是 01111111 第一个字节第一位为 0 的都是单字节字符
begin
single := AnsiChar(b1);
Result := Result + single + #13#10;
index_in_single := 0; //重置位置
Continue;
end;

end;

if index_in_single = 1 then
begin
b2 := b;
if b1 < $e0 then //0x07ff //单字节字符 //0x07ff的二进制是 0111 11111111 //这个算法很奇怪,并没有用完全部的第二个字节
//它的意思是说第一个字节全部用完都为1的情况下用第2个字节的一半表示双字节的字符
begin
single := AnsiChar(b1) + AnsiChar(b2);
single := Utf8ToAnsi(single); // + '| - 占用 2 字节'; //这个是为了还原为 gbk 而已
Result := Result + single + #13#10;
index_in_single := 0; //重置位置
Continue;
end;

end;

if index_in_single = 2 then
begin
b3 := b;
if b1 < $f0 then //0x07ff //单字节字符 //0x07ff的二进制是 0111 11111111 //这个算法很奇怪,并没有用完全部的第二个字节
//它的意思是说第一个字节全部用完都为1的情况下用第2个字节的一半表示双字节的字符
begin
single := AnsiChar(b1) + AnsiChar(b2) + AnsiChar(b3);
single := Utf8ToAnsi(single); // + '| - 占用 3 字节'; //这个是为了还原为 gbk 而已
Result := Result + single + #13#10;
index_in_single := 0; //重置位置
Continue;
end;

end;

if index_in_single = 3 then
begin
b4 := b;
if b1 < $f8 then //目前来说其实不用判断也可以,除非以后扩展到超过 4 个字节了
//
begin
single := AnsiChar(b1) + AnsiChar(b2) + AnsiChar(b3) + AnsiChar(b4);
single := Utf8ToAnsi(single); // + '| - 占用 4 字节'; //这个是为了还原为 gbk 而已
Result := Result + single + #13#10;
index_in_single := 0; //重置位置
Continue;
end;

end;

//2020/7/3 21:39:28
//F0 9F 94 A5 -- 这个 utf8 的笑脸符号确实是占 4 个字节


index_in_single := index_in_single + 1;
end;

end;

function Utf8Decode_manual(s:string):string;
var
sl:TStringList;
i:Integer;
line:string;
begin
//
sl := TStringList.Create;

sl.Text := decode_utf8_to_single_2020(s);

for i:=0 to sl.Count-1 do
begin
line := sl.Strings[i];
//line := Utf8ToAnsi(line); //不用,里面转换过了

Result := Result + line;
end;


sl.Free;
end;

end.




总数:0 页次:1/0 首页 尾页  
总数:0 页次:1/0 首页 尾页  


所在合集/目录



发表评论:
文本/html模式切换 插入图片 文本/html模式切换


附件:



NEWBT官方QQ群1: 276678893
可求档连环画,漫画;询问文本处理大师等软件使用技巧;求档softhub软件下载及使用技巧.
但不可"开车",严禁国家敏感话题,不可求档涉及版权的文档软件.
验证问题说明申请入群原因即可.

Copyright © 2005-2020 clq, All Rights Reserved
版权所有
桂ICP备15002303号-1