使用UTF8String转换非规范化字符

如何解决使用UTF8String转换非规范化字符

将以UTF-8编码的表情符号转换为字符串时,我们没有使用UTF8ToString获得正确的字符。我们从外部接口接收这些UTF8字符。 我们使用在线UTF8解码器测试了UTF字符,并发现它们包含正确的字符。我怀疑这些是复合字符。

procedure TestUTF8Convertion;
const
  utf8Denormalized: RawByteString = #$ED#$A0#$BD#$ED#$B8#$85#$20 + #$ED#$A0#$BD#$ED#$B8#$86#$20 + #$ED#$A0#$BD#$ED#$B8#$8A;
  utf8Normalized: RawByteString = #$F0#$9F#$98#$85 + #$F0#$9F#$98#$86 + #$F0#$9F#$98#$8A;
begin
  Memo1.Lines.Add(UTF8ToString(utf8Denormalized));
  Memo1.Lines.Add(UTF8ToString(utf8Normalized));
end;

备忘录1中的输出:

非正规化:������������

归一化:???

基于WinApi函数MultiByteToWideChar编写自己的转换函数不能解决此问题。

function UTF8DenormalizedToString(s: PAnsiChar): string;
var
  pwc: PWideChar;
  len: cardinal;
begin
  GetMem(pwc,(Length(s) + 1) * SizeOf(WideChar));
  len := MultiByteToWideChar(CP_UTF8,MB_PRECOMPOSED,@s[0],-1,pwc,length(s));
  SetString(result,len);
  FreeMem(pwc);
end;

解决方法

#$ED#$A0#$BD是Unicode代码点U+D83D的UTF-8编码形式,是高替代

#$ED#$B8#$85是Unicode代码点U+DE05的UTF-8编码形式,是低替代

#$F0#$9F#$98#$85是Unicode代码点U+1F605的UTF-8编码形式。

保留替代范围内的

Unicode代码点以供UTF-16和非法单独使用,这就是打印时看到的原因。

这些替代恰好是Unicode代码点U + 1F605(?)的正确UTF-16替代。

因此,您所遇到的是双重编码问题,需要在生成UTF-8数据的源处解决此问题。首先将U+1F605编码为UTF-16,而不是UTF-8,然后将其替代物作为Unicode代码点进行误处理并分别编码为UTF-8。相反,您想要的是将代码点U+1F605直接原样编码为UTF-8。

如果您无法修复UTF-8数据的来源,则只需手动检测此格式错误的编码,然后将数据作为UTF-16处理即可。将UTF-8数据解码为UTF-32,如果结果包含任何替代代码点,则创建一个长度相同的单独的UTF-16字符串,并将代码点原样复制到该字符串中,将其值截断为16位。然后,您可以根据需要使用该UTF-16字符串。否则,如果没有替代项,则可以正常地将UTF-8直接解码为UTF-16字符串,并改用该结果。

更新:如@AmigoJack的回答所述,此数据使用CESU-8编码(在源接口中有记录吗?)。因此,现在就知道了这一点,您就可以放弃手动检测,并假设来自此源的所有UTF-8数据都是CESU-8并按照我上面的描述进行手动解码(MultiByteToWideChar()和Delphi RTL都将无法可以自动为您处理),至少要等到界面修复好为止,例如:

function UTF8DenormalizedToString(s: PAnsiChar): UnicodeString;
var
  utf32: UCS4String;
  len,i: Integer;
begin
  utf32 := ... decode utf8 to utf32 ...; // I leave this as an exercise for you!
  len := Length(utf32) - 1; // UCS4String includes a null terminator
  SetLength(Result,len);
  for i := 1 to len do
    Result[i] := WideChar(utf32[i-1] and $FFFF); // UCS4String is 0-indexed
end;
,
  • UTF-8每个字符包含1、2、3或4个字节。码点U + 1F605已正确编码为#$F0#$9F#$98#$85
  • UTF-16每个字符包含2或4个字节。需要4个字节序列来编码超出U + FFFF的代码点(例如大多数Emojis)。仅UCS-2限于代码点U + 0000到U + FFFF(这适用于2000年之前的Windows NT版本)。
  • #$ED#$A0#$BD#$ED#$B8#$85(UTF-8高替代,然后低替代)之类的序列不是有效的UTF-8,而是CESU-8-它是天真的结果,因此从UTF-16转换不正确转换为UTF-8:而不是(识别并)将4字节的UTF-16序列(编码一个代码点)转换为4字节的UTF-8序列,并且始终转换2字节,将2x2字节转换为无效的6字节UTF- 8个序列。

将有效的UTF-8序列#$F0#$9F#$98#$85转换为有效的UTF-16序列#$3d#$d8#$05#$de对我有用。当然,请确保使用正确的字体,该字体实际上可以呈现Emojis:

// const CP_UTF8= 65001;

function Utf8ToUtf16( const sIn: AnsiString; iSrcCodePage: DWord= CP_UTF8 ): WideString;
var
  iLenDest,iLenSrc: Integer;
begin
  // First calculate how much space is needed
  iLenSrc:= Length( sIn );
  iLenDest:= MultiByteToWideChar( iSrcCodePage,PAnsiChar(sIn),iLenSrc,nil,0 );

  // Now provide the accurate space
  SetLength( result,iLenDest );
  if iLenDest> 0 then begin  // Otherwise ERROR_INVALID_PARAMETER might occur
    if MultiByteToWideChar( iSrcCodePage,PWideChar(result),iLenDest )= 0 then begin
      // GetLastError();
      result:= '';
    end;
  end;
end;

...
  Edit1.Font.Name:= 'Segoe UI Symbol';  // Already available in Win7
  Edit1.Text:= Utf8ToUtf16( AnsiString(#$F0#$9F#$98#$85' vs. '#$ED#$A0#$BD#$ED#$B8#$85) );
  // Should display: ? vs. ����

据我所知,Windows既没有CESU-8的代码页,也没有WTF-8的代码页,因此不会处理无效的UTF-8。同样不鼓励使用MB_PRECOMPOSED,并且无论如何不适用于这种情况。

与任何给您提供无效UTF-8的人交谈,并要求使他的工作正确(或立即给您UTF-16)。否则,您必须通过扫描传入的UTF-8以查找匹配的代理对来对其进行预处理,然后将这些字节替换为正确的序列。不是没有,甚至没有那么困难,但是耐心的枯燥无味。

,

如果缓冲区中有CESU-8数据,并且需要将其转换为UTF-8,则可以用单个UTF-8编码的char替换代理对。其余数据可以保留不变。

在这种情况下,您的表情符号是这样的:

  • 代码点:01 F6 05
  • UTF-8:F0 9F 98 85
  • UTF-16:D8 3D DE 05
  • CESU-8:ED A0 BD ED B8 85

CESU-8中的最高代理具有以下数据:$ 003D

CESU-8中的最低代理具有以下数据:$ 0205

正如雷米(Remy)和AmigoJack指出的那样,当您解码表情符号的UTF-16版本时,您会发现这些值。

对于UTF-16,您还需要将$ 003D值乘以$ 400(shl 10),将结果添加到$ 0205,然后在最终结果中添加$ 10000,以获得代码点。

一旦有了代码点,就可以将其转换为4字节UTF-8值集。

function ValidHighSurrogate(const aBuffer: array of AnsiChar; i: integer): boolean;
var
  n: byte;
begin
  Result := False;
  if (ord(aBuffer[i]) <> $ED) then
    exit;

  n := ord(aBuffer[i + 1]) shr 4;
  if ((n and $A) <> $A) then
    exit;

  n := ord(aBuffer[i + 2]) shr 6;
  if ((n and $2) = $2) then
    Result := True;
end;

function ValidLowSurrogate(const aBuffer: array of AnsiChar; i: integer): boolean;
var
  n: byte;
begin
  Result := False;
  if (ord(aBuffer[i]) <> $ED) then
    exit;

  n := ord(aBuffer[i + 1]) shr 4;
  if ((n and $B) <> $B) then
    exit;

  n := ord(aBuffer[i + 2]) shr 6;
  if ((n and $2) = $2) then
    Result := True;
end;

function GetRawSurrogateValue(const aBuffer: array of AnsiChar; i: integer): integer;
var
  a,b: integer;
begin
  a := ord(aBuffer[i + 1]) and $0F;
  b := ord(aBuffer[i + 2]) and $3F;

  Result := (a shl 6) or b;
end;

function CESU8ToUTF8(const aBuffer: array of AnsiChar): boolean;
var
  TempBuffer: array of AnsiChar;
  i,j,TempLen: integer;
  TempHigh,TempLow,TempCodePoint: integer;
begin
  TempLen := length(aBuffer);
  SetLength(TempBuffer,TempLen);

  i := 0;
  j := 0;
  while (i < TempLen) do
    if (i + 5 < TempLen) and ValidHighSurrogate(aBuffer,i) and
      ValidLowSurrogate(aBuffer,i + 3) then
    begin
      TempHigh := GetRawSurrogateValue(aBuffer,i);
      TempLow := GetRawSurrogateValue(aBuffer,i + 3);
      TempCodePoint := (TempHigh shl 10) + TempLow + $10000;
      TempBuffer[j] := AnsiChar($F0 + ((TempCodePoint and $1C0000) shr 18));
      TempBuffer[j + 1] := AnsiChar($80 + ((TempCodePoint and $3F000) shr 12));
      TempBuffer[j + 2] := AnsiChar($80 + ((TempCodePoint and $FC0) shr 6));
      TempBuffer[j + 3] := AnsiChar($80 + (TempCodePoint and $3F));
      inc(j,4);
      inc(i,6);
    end
    else
    begin
      TempBuffer[j] := aBuffer[i];
      inc(i);
      inc(j);
    end;

  Result := < save the buffer here >;
end;

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-