В старые времена у меня была функция, которая преобразует WideString
в AnsiString
указанной кодовой страницы:
function WideStringToString(const Source: WideString; CodePage: UINT): AnsiString;
...
begin
...
// Convert source UTF-16 string (WideString) to the destination using the code-page
strLen := WideCharToMultiByte(CodePage, 0,
PWideChar(Source), Length(Source), //Source
PAnsiChar(cpStr), strLen, //Destination
nil, nil);
...
end;
И все сработало. Я передал функцию строку юникода (т.е. Кодированные данные UTF-16) и преобразовал ее в AnsiString
с пониманием того, что байты в AnsiString
представлены символами с указанной кодовой страницы.
Например:
TUnicodeHelper.WideStringToString('Ŧĥε qùíçķ ƀřǭŵņ fôx ǰűmpεď ōvêŗ ţħě łáƶÿ ďơǥ', 1252);
возвращает Windows-1252
закодированную строку:
The qùíçk brown fôx jumped ovêr the lázÿ dog
Примечание. Информация, конечно же, была потеряна во время преобразования из полного набора символов Юникода в ограниченные пределы кодовой страницы Windows-1252:
Ŧĥε qùíçķ ƀřǭŵņ fôx ǰűmpεď ōvêŗ ţħě łáƶÿ ďơǥ
(до)The qùíçk brown fôx jumped ovêr the lázÿ dog
(после)
Но Windows WideChartoMultiByte
выполняет довольно хорошую работу по наилучшему сопоставлению; как это предусмотрено.
Теперь после времени
Теперь мы находимся в разы. WideString
теперь является парией, а UnicodeString
- добротой. Это несущественное изменение; поскольку функция Windows требовала только указателя на серию WideChar
(что также есть UnicodeString
). Поэтому вместо объявления UnicodeString
мы используем декларацию:
funtion WideStringToString(const Source: UnicodeString; CodePage: UINT): AnsiString;
begin
...
end;
Теперь мы возвращаемся к возвращаемому значению. У меня есть AnsiString
, который содержит байты:
54 68 65 20 71 F9 ED E7 The qùíç
6B 20 62 72 6F 77 6E 20 k brown
66 F4 78 20 6A 75 6D 70 fôx jump
65 64 20 6F 76 EA 72 20 ed ovêr
74 68 65 20 6C E1 7A FF the lázÿ
20 64 6F 67 dog
В прежние времена это было хорошо. Я отслеживал, какая кодовая страница AnsiString
действительно содержалась; я должен был помнить, что возвращенный AnsiString
не был закодирован с использованием локали компьютера (например, Windows 1258), но вместо этого был закодирован с использованием другой кодовой страницы (кодовая страница CodePage
).
Но в Delphi XE6 an AnsiString
также тайно содержит кодовую страницу:
- codePage: 1258
- длина: 44
- значение:
The qùíçk brown fôx jumped ovêr the lázÿ dog
Эта кодовая страница неверна. Delphi указывает кодовую страницу моего компьютера, а не кодовую страницу, в которой находится строка. Технически это не проблема, я всегда понимал, что AnsiString
был на определенной кодовой странице, я просто должен был обязательно передать эту информацию.
Поэтому, когда я хотел декодировать строку, мне пришлось пройти по кодовой странице с ней:
s := TUnicodeHeper.StringToWideString(s, 1252);
с
function StringToWideString(s: AnsiString; CodePage: UINT): UnicodeString;
begin
...
MultiByteToWideChar(...);
...
end;
Затем один человек закручивает все вверх
Проблема заключалась в том, что в прежние времена я объявлял тип с именем Utf8String
:
type
Utf8String = type AnsiString;
Потому что достаточно распространено:
function TUnicodeHelper.WideStringToUtf8(const s: UnicodeString): Utf8String;
begin
Result := WideStringToString(s, CP_UTF8);
end;
и наоборот:
function TUnicodeHelper.Utf8ToWideString(const s: Utf8String): UnicodeString;
begin
Result := StringToWideString(s, CP_UTF8);
end;
Теперь в XE6 у меня есть функция, которая принимает a Utf8String
. Если какой-то существующий код где-то взял кодировку UTF-8 AnsiString
и попытался преобразовать ее в UnicodeString с помощью Utf8ToWideString
, это завершится неудачно:
s: AnsiString;
s := UnicodeStringToString('Ŧĥε qùíçķ ƀřǭŵņ fôx ǰűmpεď ōvêŗ ţħě łáƶÿ ďơǥ', CP_UTF8);
...
ws: UnicodeString;
ws := Utf8ToWideString(s); //Delphi will treat s an CP1252, and convert it to UTF8
Или, что еще хуже, это ширина существующего кода:
s: Utf8String;
s := UnicodeStringToString('Ŧĥε qùíçķ ƀřǭŵņ fôx ǰűmpεď ōvêŗ ţħě łáƶÿ ďơǥ', CP_UTF8);
Возвращенная строка будет полностью искажена:
- функция возвращает
AnsiString(1252)
(AnsiString
, помеченную как закодированная с использованием текущей кодовой страницы) - результат возврата сохраняется в строке
AnsiString(65001)
(Utf8String
) - Delphi преобразует кодированную строку UTF-8 в UTF-8, как если бы она была 1252.
Как двигаться вперед
В идеале моя функция UnicodeStringToString(string, codePage)
(которая возвращает AnsiString
) может установить CodePage
внутри строки, чтобы она соответствовала фактической кодовой странице, используя что-то вроде SetCodePage
:
function UnicodeStringToString(s: UnicodeString; CodePage: UINT): AnsiString;
begin
...
WideCharToMultiByte(...);
...
//Adjust the codepage contained in the AnsiString to match reality
//SetCodePage(Result, CodePage, False); SetCodePage only works on RawByteString
if Length(Result) > 0 then
PStrRec(PByte(Result) - SizeOf(StrRec)).codePage := CodePage;
end;
За исключением того, что ручная работа с внутренней структурой AnsiString
опасна.
Так как насчет возврата RawByteString
?
Было сказано, что многие люди, которые не я, который RawByteString
должен быть универсальным получателем; он не должен быть как возвращаемый параметр:
function UnicodeStringToString(s: UnicodeString; CodePage: UINT): RawByteString;
begin
...
WideCharToMultiByte(...);
...
//Adjust the codepage contained in the AnsiString to match reality
SetCodePage(Result, CodePage, False); SetCodePage only works on RawByteString
end;
Это означает, что вы можете использовать поддерживаемый и документированный SetCodePage
.
Но если мы собираемся пересечь строку и начать возвращать RawByteString
, конечно, у Delphi уже есть функция, которая может преобразовать строку UnicodeString
в строку RawByteString
и наоборот:
function WideStringToString(const s: UnicodeString; CodePage: UINT): RawByteString;
begin
Result := SysUtils.Something(s, CodePage);
end;
function StringToWideString(const s: RawByteString; CodePage: UINT): UnicodeString;
begin
Result := SysUtils.SomethingElse(s, CodePage);
end;
Но что это такое?
Или что еще я должен делать?
Это был длинный набор фона для тривиального вопроса. Реальный вопрос, конечно, что я должен делать вместо этого? Существует много кода, который зависит от UnicodeStringToString
и обратного.
ТЛ; др:
Я могу преобразовать a UnicodeString
в UTF, выполнив:
Utf8Encode('Ŧĥε qùíçķ ƀřǭŵņ fôx ǰűmpεď ōvêŗ ţħě łáƶÿ ďơǥ');
и я могу преобразовать UnicodeString
на текущую страницу кода, используя:
AnsiString('Ŧĥε qùíçķ ƀřǭŵņ fôx ǰűmpεď ōvêŗ ţħě łáƶÿ ďơǥ');
Но как мне преобразовать UnicodeString
в произвольную (неуказанную) кодовую страницу?
Я чувствую, что, поскольку все действительно является AnsiString
:
Utf8String = AnsiString(65001);
RawByteString = AnsiString(65535);
Я должен укусить пулю, распаковать структуру AnsiString
и вставить в нее правильную кодовую страницу:
function StringToAnsi(const s: UnicodeString; CodePage: UINT): AnsiString;
begin
LocaleCharsFromUnicode(CodePage, ..., s, ...);
...
if Length(Result) > 0 then
PStrRec(PByte(Result) - SizeOf(StrRec)).codePage := CodePage;
end;
Затем остальная часть VCL будет падать в линию.