Dans les temps anciens, j'avais une fonction qui convertissait un WideString
en AnsiString
de la page de code spécifiée:
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;
Et tout a fonctionné. J'ai passé la fonction une chaîne unicode (c'est-à-dire des données encodées UTF-16) et je l'ai convertie en AnsiString
, étant entendu que les octets dans le AnsiString
caractères représentés de la page de codes spécifiée.
Par exemple:
TUnicodeHelper.WideStringToString('Ŧĥε qùíçķ ƀřǭŵņ fôx ǰűmpεď ōvêŗ ţħě łáƶÿ ďơǥ', 1252);
retournerait la chaîne encodée Windows-1252
:
The qùíçk brown fôx jumped ovêr the lázÿ dog
Remarque: Des informations ont bien sûr été perdues lors de la conversion du jeu de caractères Unicode complet aux limites limitées de la page de codes Windows-1252:
Ŧĥε qùíçķ ƀřǭŵņ fôx ǰűmpεď ōvêŗ ţħě łáƶÿ ďơǥ
(avant)The qùíçk brown fôx jumped ovêr the lázÿ dog
(après)
Mais Windows WideChartoMultiByte
fait un très bon travail de mappage optimal; comme il est conçu pour le faire.
Maintenant, nous sommes dans l'après-temps. WideString
est maintenant un paria, avec UnicodeString
étant la bonté. C'est un changement sans conséquence; car la fonction Windows n'avait besoin que d'un pointeur vers une série de WideChar
de toute façon (qui est également un UnicodeString
). Nous changeons donc la déclaration pour utiliser UnicodeString
à la place:
funtion WideStringToString(const Source: UnicodeString; CodePage: UINT): AnsiString;
begin
...
end;
Nous arrivons maintenant à la valeur de retour. j'ai un AnsiString
qui contient les octets:
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
Autrefois, c'était bien. J'ai gardé une trace de la page de code que le AnsiString
contenait réellement; je devais rappelez-vous que le AnsiString
retourné n'était pas encodé à l'aide des paramètres régionaux de l'ordinateur (par exemple Windows 1258), mais à la place est encodé à l'aide d'une autre page de code (le CodePage
page de codes).
Mais dans Delphi XE6, un AnsiString
contient également secrètement la page de code:
The qùíçk brown fôx jumped ovêr the lázÿ dog
Cette page de codes est incorrecte. Delphi spécifie la page de codes de mon ordinateur, plutôt que la page de codes de la chaîne. Techniquement, ce n'est pas un problème, j'ai toujours compris que le AnsiString
était dans une page de code particulière, je devais juste être sûr de transmettre cette information.
Donc, quand je voulais décoder la chaîne, je devais passer la page de code avec:
s := TUnicodeHeper.StringToWideString(s, 1252);
avec
function StringToWideString(s: AnsiString; CodePage: UINT): UnicodeString;
begin
...
MultiByteToWideChar(...);
...
end;
Le problème était que dans les temps anciens, j'ai déclaré un type appelé Utf8String
:
type
Utf8String = type AnsiString;
Parce qu'il était assez courant d'avoir:
function TUnicodeHelper.WideStringToUtf8(const s: UnicodeString): Utf8String;
begin
Result := WideStringToString(s, CP_UTF8);
end;
et l'inverse:
function TUnicodeHelper.Utf8ToWideString(const s: Utf8String): UnicodeString;
begin
Result := StringToWideString(s, CP_UTF8);
end;
Maintenant, dans XE6, j'ai une fonction qui prend un Utf8String
. Si du code existant quelque part prenait un AnsiString
encodé en UTF-8 et essayait de le convertir en UnicodeString en utilisant Utf8ToWideString
, Il échouerait:
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
Ou pire, est l'étendue du code existant qui fait:
s: Utf8String;
s := UnicodeStringToString('Ŧĥε qùíçķ ƀřǭŵņ fôx ǰűmpεď ōvêŗ ţħě łáƶÿ ďơǥ', CP_UTF8);
La chaîne retournée deviendra totalement mutilée:
AnsiString(1252)
(AnsiString
étiquetée comme encodée à l'aide de la page de codes actuelle)AnsiString(65001)
(Utf8String
)Idéalement, ma fonction UnicodeStringToString(string, codePage)
(qui renvoie un AnsiString
) pourrait définir CodePage
à l'intérieur de la chaîne pour correspondre à la page de code réelle en utilisant quelque chose comme 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;
Sauf que le nettoyage manuel avec la structure interne d'un AnsiString
est horriblement dangereux.
RawByteString
?Il a été dit, à plusieurs reprises, par beaucoup de gens qui ne sont pas moi que RawByteString
est censé être le destinataire universel; ce n'était pas censé être un paramètre de retour:
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;
Cela a l'avantage de pouvoir utiliser le SetCodePage
supporté et documenté.
Mais si nous allons franchir une ligne et commencer à renvoyer RawByteString
, Delphi a sûrement déjà une fonction qui peut convertir une UnicodeString
en une chaîne de RawByteString
et vice 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;
Mais qu'est-ce que c'est?
Ce fut un ensemble de fond de longue haleine pour une question triviale. La question réelle est, bien sûr, que dois-je faire à la place? Il y a beaucoup de code qui dépend du UnicodeStringToString
et de l'inverse.
Je peux convertir un UnicodeString
en UTF en faisant:
Utf8Encode('Ŧĥε qùíçķ ƀřǭŵņ fôx ǰűmpεď ōvêŗ ţħě łáƶÿ ďơǥ');
et je peux convertir un UnicodeString
vers la page de codes actuelle en utilisant:
AnsiString('Ŧĥε qùíçķ ƀřǭŵņ fôx ǰűmpεď ōvêŗ ţħě łáƶÿ ďơǥ');
Mais comment convertir un UnicodeString
en une page de code arbitraire (non spécifiée)?
Mon sentiment est que puisque tout est vraiment un AnsiString
:
Utf8String = AnsiString(65001);
RawByteString = AnsiString(65535);
je devrais mordre la balle, ouvrir la structure AnsiString
et y insérer la page de code correcte:
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;
Ensuite, le reste de la VCL tombera en ligne.
Dans ce cas particulier, l'utilisation de RawByteString
est une solution appropriée:
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;
De cette façon, le RawByteString
contient la page de code et l'attribution du RawByteString
à tout autre type de chaîne, que ce soit AnsiString
ou UTF8String
Ou autre, permettra la RTL pour convertir automatiquement les données RawByteString
de sa page de codes actuelle vers la page de codes de la chaîne de destination (qui inclut les conversions en UnicodeString
).
Si vous devez absolument renvoyer un AnsiString
(ce que je ne recommande pas), vous pouvez toujours utiliser SetCodePage()
via un transtypage:
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;
L'inverse est beaucoup plus facile, utilisez simplement la page de code déjà stockée dans un (Ansi|RawByte)String
(Assurez-vous simplement que ces pages de code sont toujours précises), car la RTL sait déjà comment récupérer et utiliser la page de code pour vous:
function StringToWideString(const Source: AnsiString): UnicodeString;
begin
Result := UnicodeString(Source);
end;
function StringToWideString(const Source: RawByteString): UnicodeString;
begin
Result := UnicodeString(Source);
end;
Cela étant dit, je suggérerais de supprimer complètement les fonctions d'assistance et d'utiliser simplement des chaînes tapées à la place. Laissez le RTL gérer les conversions pour vous:
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;
Je pense que retourner un RawByteString
est probablement aussi bon que vous en aurez. Vous pouvez le faire en utilisant AnsiString
comme vous l'avez indiqué, mais RawByteString
capture mieux l'intention. Dans ce scénario, un RawByteString
compte moralement comme un paramètre au sens des conseils officiels d'Embarcadero. C'est juste une sortie plutôt qu'une entrée. La vraie clé est de ne pas l'utiliser comme variable.
Vous pouvez le coder comme ceci:
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;
Alors
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));
sorties 1252, 1251, puis 65001 comme vous vous en doutez.
Et vous pouvez utiliser LocaleCharsFromUnicode
si vous préférez. Bien sûr, vous devez prendre sa documentation avec une pincée de sel: LocaleCharsFromUnicode est un wrapper pour la fonction WideCharToMultiByte . Étonnant que le texte ait été écrit depuis que LocaleCharsFromUnicode
n'existe sûrement que pour être multi-plateforme.
Cependant, je me demande si vous pouvez faire une erreur en essayant de conserver le texte encodé ANSI dans les variables AnsiString
dans votre programme. Normalement, vous seriez encodé en ANSI le plus tard possible (à la limite d'interopérabilité) et décoderiez également le plus tôt possible.
Si vous devez simplement le faire, il existe peut-être une meilleure solution qui évite complètement le redouté AnsiString
. Au lieu de stocker le texte dans un AnsiString
, stockez-le dans TBytes
. Vous disposez déjà de structures de données qui assurent le suivi de l'encodage, alors pourquoi ne pas les conserver. Remplacez l'enregistrement qui contient la page de codes et AnsiString
par celui contenant la page de codes et TBytes
. Vous ne craindriez alors rien de recoder votre texte derrière votre dos. Et votre code sera prêt à être utilisé sur les compilateurs mobiles.
Se promener à travers System.pas
, j'ai trouvé la fonction intégrée SetAnsiString
qui fait ce que je veux:
procedure SetAnsiString(Dest: _PAnsiStr; Source: PWideChar; Length: Integer; CodePage: Word);
Il est également important de noter que cette fonction le fait Poussez le CodePage dans la structure StrRec interne pour moi:
PStrRec(PByte(Dest) - SizeOf(StrRec)).codePage := CodePage;
Cela me permet d'écrire quelque chose comme:
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;
Alors quand j'appelle:
actual := WideStringToString('Ŧĥε qùíçķ ƀřǭŵņ fôx ǰűmpεď ōvêŗ ţħě łáƶÿ ďơǥ', 850);
j'obtiens le AnsiString
résultant:
codePage: $0352 (850)
elemSize: $0001 (1)
refCnt: $00000001 (1)
length: $0000002C (44)
contents: 'The qùíçk brown fôx jumped ovêr the láZÿ dog'
Un AnsiString avec la page de code appropriée déjà remplie dans le membre secret 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;
Remarque : Tout code publié dans le domaine public. Aucune attribution requise.