Преобразование 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;
и все работало. Я передал функцию a unicode string (т. е. кодированные данные 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
(после)
и WideChartoMultiByte
делает довольно хорошую работу наиболее подходящего отображения; как он предназначен для этого.
теперь после раз
теперь мы находимся в after times. WideString
теперь пария, с UnicodeString
быть добродетелью. Это несущественное изменение; поскольку функция Windows нуждалась только в указатель серия WideChar
в любом случае (которого UnicodeString
тоже есть). Поэтому мы меняем декларацию на use 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
также тайно содержит кодовую страницу:
- кодовая страница: 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
строку и обратно versa:
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;
но что это?
или что еще мне делать?
это был длинный набор фона для тривиального вопроса. The реальные вопрос, конечно, что я должен делать вместо этого? Существует много кода, который зависит от UnicodeStringToString
и наоборот.
tl; dr:
я могу преобразовать 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 будет соответствовать.
3 ответов
в данном конкретном случае, используя RawByteString
соответствующее решение:
function WideStringToString(const Source: UnicodeString; CodePage: UINT): RawByteString;
var
strLen: Integer;
begin
strLen := LocaleCharsFromUnicode(CodePage, 0, PWideChar(Source), Length(Source), nil, 0, nil, nil));
if strLen > 0 then
begin
SetLength(Result, strLen);
LocaleCharsFromUnicode(CodePage, 0, PWideChar(Source), Length(Source), PAnsiChar(Result), strLen, nil, nil));
SetCodePage(Result, CodePage, False);
end;
end;
таким образом,RawByteString
держит codepage и назначение RawByteString
для любого другого типа String, будь то AnsiString
или UTF8String
или что угодно, позволит RTL автоматически преобразовать RawByteString
данные из текущей кодовой страницы в кодовую страницу строки назначения (которая включает преобразования в UnicodeString
).
если вы абсолютно должны возвратить AnsiString
(что я и делаю не рекомендую), вы все равно можете использовать SetCodePage()
через typecast:
function WideStringToString(const Source: UnicodeString; CodePage: UINT): AnsiString;
var
strLen: Integer;
begin
strLen := LocaleCharsFromUnicode(CodePage, 0, PWideChar(Source), Length(Source), nil, 0, nil, nil));
if strLen > 0 then
begin
SetLength(Result, strLen);
LocaleCharsFromUnicode(CodePage, 0, PWideChar(Source), Length(Source), PAnsiChar(Result), strLen, nil, nil));
SetCodePage(PRawByteString(@Result)^, CodePage, False);
end;
end;
обратное намного проще, просто используйте кодовую страницу, уже хранящуюся в (Ansi|RawByte)String
(просто убедитесь, что эти кодовые страницы всегда точны), так как RTL уже знает, как получить и использовать кодовую страницу для вас:
function StringToWideString(const Source: AnsiString): UnicodeString;
begin
Result := UnicodeString(Source);
end;
function StringToWideString(const Source: RawByteString): UnicodeString;
begin
Result := UnicodeString(Source);
end;
это, как говорится, я бы предложил вообще отказаться от вспомогательных функций и просто использовать типизированные строки. Пусть RTL обрабатывает преобразования для вас:
type
Win1252String = type AnsiString(1252);
var
s: UnicodeString;
a: Win1252String;
begin
s := 'Ŧĥε qùíçķ ƀřǭŵņ fôx ǰűmpεď ōvêŗ ţħě łáƶÿ ďơǥ';
a := Win1252String(s);
s := UnicodeString(a);
end;
var
s: UnicodeString;
u: UTF8String;
begin
s := 'Ŧĥε qùíçķ ƀřǭŵņ fôx ǰűmpεď ōvêŗ ţħě łáƶÿ ďơǥ';
u := UTF8String(s);
s := UnicodeString(u);
end;
я думаю, что возврат RawByteString
вероятно, так же хорошо, как вы получите. Вы можете сделать это с помощью AnsiString
как вы изложили, но RawByteString
захватывает намерение лучше. В этом случае RawByteString
морально считается параметром в смысле официального Совета Embarcadero. Это просто выход, а не вход. Реальный ключ не использовать его в качестве переменной.
вы могли бы код такой:
function MBCSString(const s: UnicodeString; CodePage: Word): RawByteString;
var
enc: TEncoding;
bytes: TBytes;
begin
enc := TEncoding.GetEncoding(CodePage);
try
bytes := enc.GetBytes(s);
SetLength(Result, Length(bytes));
Move(Pointer(bytes)^, Pointer(Result)^, Length(bytes));
SetCodePage(Result, CodePage, False);
finally
enc.Free;
end;
end;
затем
var
s: AnsiString;
....
s := MBCSString('Ŧĥε qùíçķ ƀřǭŵņ fôx ǰűmpεď ōvêŗ ţħě łáƶÿ ďơǥ', 1252);
Writeln(StringCodePage(s));
s := MBCSString('Ŧĥε qùíçķ ƀřǭŵņ fôx ǰűmpεď ōvêŗ ţħě łáƶÿ ďơǥ', 1251);
Writeln(StringCodePage(s));
s := MBCSString('Ŧĥε qùíçķ ƀřǭŵņ fôx ǰűmpεď ōvêŗ ţħě łáƶÿ ďơǥ', 65001);
Writeln(StringCodePage(s));
выходы 1252, 1251, и затем 65001, как и следовало ожидать.
и вы могли бы использовать LocaleCharsFromUnicode
если вы предпочитаете. Конечно, вам нужно взять документация с щепоткой соли: LocaleCharsFromUnicode-это оболочка для функции WideCharToMultiByte. Удивительно, что текст был написан с LocaleCharsFromUnicode
конечно, существует только кросс-платформенный.
тем не менее, мне интересно, может быть, вы делаете ошибку, пытаясь сохранить кодированный текст ANSI в AnsiString
переменных в вашей программе. Обычно вы кодируете ANSI как можно позже (на границе взаимодействия), а также декодируете как можно раньше.
если вы просто должны сделать это, то, возможно, есть лучшее решение, чтобы избежать ужасной AnsiString
полностью. Вместо того чтобы хранить текст в AnsiString
, магазина в TBytes
. У вас уже есть структуры данных, которые отслеживают кодировку, так почему бы не сохранить их. Замените запись, содержащую кодовую страницу и AnsiString
С один, содержащий кодовую страницу и TBytes
. Тогда вы не будете бояться, что что-то перекодирует ваш текст за вашей спиной. И ваш код будет готов к использованию на мобильных компиляторов.
самоунижение через System.pas
, Я нашел встроенную функцию SetAnsiString
есть:
procedure SetAnsiString(Dest: _PAnsiStr; Source: PWideChar; Length: Integer; CodePage: Word);
также важно отметить, что эта функция тут вставьте кодовую страницу во внутреннюю структуру StrRec для меня:
PStrRec(PByte(Dest) - SizeOf(StrRec)).codePage := CodePage;
это позволяет мне писать что-то вроде:
function WideStringToString(const s: UnicodeString; DestinationCodePage: Word): AnsiString;
var
strLen: Integer;
begin
strLen := Length(Source);
if strLen = 0 then
begin
Result := '';
Exit;
end;
//Delphi XE6 has a function to convert a unicode string to a tagged AnsiString
SetAnsiString(@Result, @Source[1], strLen, DestinationCodePage);
end;
поэтому, когда я называю:
actual := WideStringToString('Ŧĥε qùíçķ ƀřǭŵņ fôx ǰűmpεď ōvêŗ ţħě łáƶÿ ďơǥ', 850);
я получаю результат AnsiString
:
codePage: 52 (850)
elemSize: 01 (1)
refCnt: 000001 (1)
length: 00002C (44)
contents: 'The qùíçk brown fôx jumped ovêr the láZÿ dog'
An AnsiString С соответствующей кодовой страницей, уже засунутой в секрет codePage
член.
иначе
class function TUnicodeHelper.ByteStringToUnicode(const Source: RawByteString; CodePage: UINT): UnicodeString;
var
wideLen: Integer;
dw: DWORD;
begin
{
See http://msdn.microsoft.com/en-us/library/dd317756.aspx
Code Page Identifiers
for a list of code pages supported in Windows.
Some common code pages are:
CP_UTF8 (65001) utf-8 "Unicode (UTF-8)"
CP_ACP (0) The system default Windows ANSI code page.
CP_OEMCP (1) The current system OEM code page.
1252 Windows-1252 "ANSI Latin 1; Western European (Windows)", this is what most of us in north america use in Windows
437 IBM437 "OEM United States", this is your "DOS fonts"
850 ibm850 "OEM Multilingual Latin 1; Western European (DOS)", the format accepted by Fincen for LCTR/STR
28591 iso-8859-1 "ISO 8859-1 Latin 1; Western European (ISO)", Windows-1252 is a super-set of iso-8859-1, adding things like euro symbol, bullet and ellipses
20127 us-ascii "US-ASCII (7-bit)"
}
if Length(Source) = 0 then
begin
Result := '';
Exit;
end;
// Determine real size of final, string in symbols
// wideLen := MultiByteToWideChar(CodePage, 0, PAnsiChar(Source), Length(Source), nil, 0);
wideLen := UnicodeFromLocaleChars(CodePage, 0, PAnsiChar(Source), Length(Source), nil, 0);
if wideLen = 0 then
begin
dw := GetLastError;
raise EConvertError.Create('[StringToWideString] Could not get wide length of UTF-16 string. Error '+IntToStr(dw)+' ('+SysErrorMessage(dw)+')');
end;
// Allocate memory for UTF-16 string
SetLength(Result, wideLen);
// Convert source string to UTF-16 (WideString)
// wideLen := MultiByteToWideChar(CodePage, 0, PAnsiChar(Source), Length(Source), PWChar(wideStr), wideLen);
wideLen := UnicodeFromLocaleChars(CodePage, 0, PAnsiChar(Source), Length(Source), PWChar(Result), wideLen);
if wideLen = 0 then
begin
dw := GetLastError;
raise EConvertError.Create('[StringToWideString] Could not convert string to UTF-16. Error '+IntToStr(dw)+' ('+SysErrorMessage(dw)+')');
end;
end;
Примечание: любой код, выпущенный в общественное достояние. Атрибуция не требуется.