【问题标题】:Convert char pos of UnicodeString to byte pos in a utf8 string将 UnicodeString 的 char pos 转换为 utf8 字符串中的字节 pos
【发布时间】:2012-05-10 07:01:23
【问题描述】:

我使用 Scintilla 并将其编码设置为 utf8(如果我理解正确,这是使其与 Unicode 字符兼容的唯一方法)。有了这个设置,当谈到文本中的 positions 时,Scintilla 表示 byte 位置。

问题是,我在程序的其余部分使用 UnicodeString,当我需要在 Scintilla 编辑器中选择特定范围时,我需要将 UnicodeString 的 char pos 转换为对应的 utf8 字符串中的 byte pos到 UnicodeString。我怎样才能轻松做到这一点?谢谢。

PS,当我找到ByteToCharIndex时,我认为这是我需要的,但是根据它的文档和我的测试结果,它只有在系统使用多字节字符系统(MBCS)时才有效。

【问题讨论】:

  • 您确定 ByteToCharIndex 不起作用吗?如果文档的文本早于 Delphi 2009,我不会感到惊讶,当时 AnsiString 更改为带有自己的代码页。现在 AnsiString 包含一个代码页,该函数应该能够判断字符串是编码为 MBCS、SBCS 还是 UTF-8,而不是依赖于系统设置。
  • @RobKennedy - 它不起作用;不仅如此,Windows 函数CharNextExA 也不适用于 UTF8。
  • 是的,Serg 确认它不起作用,我也试过了。

标签: delphi delphi-2010


【解决方案1】:

您应该使用 UTF8 description 自己解析 UTF8 字符串。我写了一个 ByteToCharIndex 的快速 UTF8 模拟并在西里尔字符串上进行了测试:

function UTF8PosToCharIndex(const S: UTF8String; Index: Integer): Integer;
var
  I: Integer;
  P: PAnsiChar;

begin
  Result:= 0;
  if (Index <= 0) or (Index > Length(S)) then Exit;
  I:= 1;
  P:= PAnsiChar(S);
  while I <= Index do begin
    if Ord(P^) and $C0 <> $80 then Inc(Result);
    Inc(I);
    Inc(P);
  end;
end;

const TestStr: UTF8String = 'abФЫВА';

procedure TForm1.Button2Click(Sender: TObject);
begin
  ShowMessage(IntToStr(UTF8PosToCharIndex(TestStr, 1))); // a = 1
  ShowMessage(IntToStr(UTF8PosToCharIndex(TestStr, 2))); // b = 2
  ShowMessage(IntToStr(UTF8PosToCharIndex(TestStr, 3))); // Ф = 3
  ShowMessage(IntToStr(UTF8PosToCharIndex(TestStr, 5))); // Ы = 4
  ShowMessage(IntToStr(UTF8PosToCharIndex(TestStr, 7))); // В = 5
end;

反转功能也没有问题:

function CharIndexToUTF8Pos(const S: UTF8String; Index: Integer): Integer;
var
  P: PAnsiChar;

begin
  Result:= 0;
  P:= PAnsiChar(S);
  while (Result < Length(S)) and (Index > 0) do begin
    Inc(Result);
    if Ord(P^) and $C0 <> $80 then Dec(Index);
    Inc(P);
  end;
  if Index <> 0 then Result:= 0;  // char index not found
end;

【讨论】:

  • 感谢塞尔格!虽然您的 UTF8PosToCharIndex 函数也绝对有用,但实际上,我在这个 SO 问题中要问的是相反的函数 CharIndexToUTF8Pos。你的 func 对我来说是一个很好的例子(我会尝试研究 utf-8 规范,看看我是否可以实现一个,或者你可以给我一些提示:)
  • @EdwinYip - 我写了一篇与您的问题相关的简短博客文章 - sergworks.wordpress.com/2012/05/01/parsing-utf8-strings
  • 感谢您的帮助,我使用从您的代码派生的函数解决了我的问题,并将其作为单独的答案发布在下面。
  • @EdwinYip 这适用于任何 AnsiString 编码还是仅适用于 Cyrlic?
【解决方案2】:

我非常尊重地基于 Serg 的代码编写了一个函数,我将其作为单独的答案发布在这里,希望它对其他人也有帮助。 Serg 的回答被接受了。

{返回字符第一个字节的索引(从1开始)(unicode点) 由 aUtf8Str 中的 aCharIdx(基于 1)指定。

Edwin Yip 根据 SO 成员 Serg (https://stackoverflow.com/users/246408/serg) 编写的代码修改了代码

参考 1:https://stackoverflow.com/a/10388131/133516

参考 2:http://sergworks.wordpress.com/2012/05/01/parsing-utf8-strings/ }

function CharPosToUTF8BytePos(const aUtf8Str: UTF8String; const aCharIdx:
    Integer): Integer;
var
  p: PAnsiChar;
  charCount: Integer;
begin
  p:= PAnsiChar(aUtf8Str);
  Result:= 0;
  charCount:= 0;
  while (Result < Length(aUtf8Str)) do
  begin
    if IsUTF8LeadChar(p^) then
      Inc(charCount);

    if charCount = aCharIdx then
      Exit(Result + 1);

    Inc(p);
    Inc(Result);
  end;
end;

【讨论】:

    【解决方案3】:

    UTF-8 和 UTF-16(UnicodeString 使用的)都是可变长度编码。给定的 Unicode 代码点可以使用 1-4 个单字节代码单元在 UTF-8 中编码,在 UTF-16 中使用 1 或 2 个 2 字节代码单元,具体取决于代码点的数值。将 UTF-16 字符串中的位置转换为等效 UTF-8 字符串中的位置的唯一方法是将位置之前的 UTF-16 代码单元解码回其原始 Unicode 代码点值,然后将它们重新编码为 UTF- 8 个代码单元。

    听起来您最好重新编写与 Scintilla 交互的代码以使用 UTF8String 而不是 UnicodeString,然后您就不必再在该层在 UTF-8 和 UTF-16 之间进行转换.在与其余代码交互时,您可以根据需要在UTF8StringUnicodeString 之间进行转换。

    【讨论】:

    • 第 3 方库。我使用handles UnicodeString...而使用UnicodeString(D2009 及更高版本中的字符串)的方便之处在于,我们不需要关心它的内部编码,我们可以将字符串视为“字符数组”,编译器会注意一个字符是否占用 2 个字节或更多字节。
    猜你喜欢
    • 2012-01-27
    • 2017-05-18
    • 2012-07-17
    • 2014-01-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-09-14
    • 2011-01-18
    相关资源
    最近更新 更多