Преобразование UnicodeString в AnsiString
В старые времена у меня была функция, которая преобразует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
Примечание: информация, конечно, была потеряна во время преобразования из полного набора символов Unicode в ограниченные пределы кодовой страницы Windows-1252:
Ŧĥε qùíçķ ƀřǭŵņ fôx ǰűmpεď ōvêŗ ţħě łáƶÿ ďơǥ
(до)The qùíçk brown fôx jumped ovêr the lázÿ dog
(после)Но винда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 XE6AnsiString
также тайно содержит кодовую страницу:
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-8AnsiString
и попробуйте преобразовать его в 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
и наоборот.
Я могу преобразовать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 попадут в линию.