delphi unicode internationalization delphi-xe6

delphi - Convertir UnicodeString a AnsiString



internationalization delphi-xe6 (3)

En los tiempos antiguos, tenía una función que convertiría una WideString en una AnsiString de la página de códigos especificada:

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;

Y todo funcionó. Pasé la función una cadena Unicode (es decir, datos codificados en UTF-16) y la convertí en una AnsiString , con el entendimiento de que los bytes en la AnsiString representaban caracteres de la página de códigos especificada.

Por ejemplo:

TUnicodeHelper.WideStringToString(''Ŧĥε qùíçķ ƀřǭŵņ fôx ǰűmpεď ōvêŗ ţħě łáƶÿ ďơǥ'', 1252);

devolvería la cadena codificada de Windows-1252 :

The qùíçk brown fôx jumped ovêr the lázÿ dog

Nota: la información, por supuesto, se perdió durante la conversión del conjunto completo de caracteres Unicode a los límites limitados de la página de códigos Windows-1252:

  • Ŧĥε qùíçķ ƀřǭŵņ fôx ǰűmpεď ōvêŗ ţħě łáƶÿ ďơǥ (antes)
  • The qùíçk brown fôx jumped ovêr the lázÿ dog (después)

Pero Windows WideChartoMultiByte hace un muy buen trabajo de mapeo de mejor ajuste; como está diseñado para hacer.

Ahora los tiempos posteriores

Ahora estamos en los tiempos posteriores. WideString ahora es un paria, siendo UnicodeString la bondad. Es un cambio intrascendente; como la función de Windows solo necesitaba un puntero a una serie de WideChar todos modos (que también es un UnicodeString ). Así que cambiamos la declaración para usar UnicodeString lugar:

funtion WideStringToString(const Source: UnicodeString; CodePage: UINT): AnsiString; begin ... end;

Ahora llegamos al valor de retorno. Tengo un AnsiString que contiene los bytes:

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

En los viejos tiempos eso estaba bien. AnsiString seguimiento de qué página de AnsiString contenía realmente el AnsiString ; Tuve que recordar que el AnsiString devuelto no se codificó con la configuración regional de la computadora (por ejemplo, Windows 1258), sino que se codificó con otra página de códigos (la página de códigos CodePage ).

Pero en Delphi XE6, AnsiString también contiene en secreto la página de códigos:

  • codePage: 1258
  • longitud: 44
  • valor: The qùíçk brown fôx jumped ovêr the lázÿ dog

Esta página de códigos es incorrecta. Delphi está especificando la página de códigos de mi computadora, en lugar de la página de códigos que es la cadena. Técnicamente, esto no es un problema, siempre entendí que el AnsiString estaba en una página de códigos en particular, solo tenía que estar seguro de pasar esa información.

Así que cuando quise descifrar la cadena, tuve que pasar la página de códigos con ella:

s := TUnicodeHeper.StringToWideString(s, 1252);

con

function StringToWideString(s: AnsiString; CodePage: UINT): UnicodeString; begin ... MultiByteToWideChar(...); ... end;

Entonces una persona lo arruina todo

El problema fue que en los tiempos anteriores declaré un tipo llamado Utf8String :

type Utf8String = type AnsiString;

Porque era bastante común tener:

function TUnicodeHelper.WideStringToUtf8(const s: UnicodeString): Utf8String; begin Result := WideStringToString(s, CP_UTF8); end;

y al revés:

function TUnicodeHelper.Utf8ToWideString(const s: Utf8String): UnicodeString; begin Result := StringToWideString(s, CP_UTF8); end;

Ahora en XE6 tengo una función que toma una Utf8String . Si algún código existente en algún lugar tomara un AnsiString codificado en UTF-8, e intente convertirlo en UnicodeString usando Utf8ToWideString , fallaría:

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

O peor, es la amplitud del código existente que hace:

s: Utf8String; s := UnicodeStringToString(''Ŧĥε qùíçķ ƀřǭŵņ fôx ǰűmpεď ōvêŗ ţħě łáƶÿ ďơǥ'', CP_UTF8);

La cadena devuelta quedará totalmente destrozada:

  • la función devuelve AnsiString(1252) ( AnsiString etiquetada como codificada utilizando la página de códigos actual)
  • el resultado devuelto se almacena en una AnsiString(65001) ( Utf8String )
  • Delphi convierte la cadena codificada en UTF-8 en UTF-8 como si fuera 1252.

Cómo avanzar

Idealmente, mi función UnicodeStringToString(string, codePage) (que devuelve un AnsiString ) podría establecer CodePage dentro de la cadena para que coincida con la página de códigos real usando algo como 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;

Excepto que la manipulación manual con la estructura interna de un AnsiString es terriblemente peligrosa.

Entonces, ¿qué hay de volver RawByteString ?

Se ha dicho, una y otra vez, por muchas personas que no soy yo que RawByteString está destinado a ser el receptor universal ; no estaba destinado a ser como un parámetro de retorno:

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;

Esto tiene la virtud de poder usar el SetCodePage documentado y SetCodePage .

Pero si vamos a cruzar una línea y comenzar a devolver RawByteString , seguramente Delphi ya tiene una función que puede convertir un UnicodeString en una cadena RawByteString y viceversa:

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;

¿Pero, qué es esto?

O que mas debo hacer?

Este fue un conjunto de antecedentes para una pregunta trivial. La verdadera pregunta es, por supuesto, ¿qué debería hacer en lugar de eso? Existe una gran cantidad de código que depende de UnicodeStringToString y de lo contrario.

tl; dr:

Puedo convertir un UnicodeString a UTF haciendo lo siguiente:

Utf8Encode(''Ŧĥε qùíçķ ƀřǭŵņ fôx ǰűmpεď ōvêŗ ţħě łáƶÿ ďơǥ'');

y puedo convertir un UnicodeString a la página de códigos actual usando:

AnsiString(''Ŧĥε qùíçķ ƀřǭŵņ fôx ǰűmpεď ōvêŗ ţħě łáƶÿ ďơǥ'');

¿Pero cómo convierto un UnicodeString a una página de códigos arbitraria (no especificada)?

Mi sensación es que ya que todo realmente es un AnsiString :

Utf8String = AnsiString(65001); RawByteString = AnsiString(65535);

Debería morder la bala, abrir la estructura AnsiString y AnsiString la página de códigos correcta:

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;

Entonces el resto de la VCL caerá en línea.


Creo que devolver un RawByteString es probablemente tan bueno como el que obtendrás. Puede hacerlo usando AnsiString como lo describió, pero RawByteString captura mejor la intención. En este escenario, un RawByteString cuenta moralmente como un parámetro en el sentido del consejo oficial de Embarcadero. Es solo una salida en lugar de una entrada. La clave real es no usarlo como una variable.

Podrías codificarlo así:

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;

Entonces

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));

salidas 1252, 1251 y luego 65001 como cabría esperar.

Y puedes usar LocaleCharsFromUnicode si lo prefieres. Por supuesto, debe tomar su documentación con una pizca de sal: LocaleCharsFromUnicode es un contenedor para la función WideCharToMultiByte . Es increíble que el texto se haya escrito ya que LocaleCharsFromUnicode seguramente solo existe para ser multiplataforma.

Sin embargo, me pregunto si puede estar cometiendo un error al tratar de mantener el texto codificado ANSI en AnsiString variables AnsiString en su programa. Normalmente, se codificaría en ANSI lo más tarde posible (en el límite de interoperabilidad), y también se decodificaría lo antes posible.

Si simplemente tiene que hacer esto, entonces quizás haya una mejor solución que evite AnsiString completo la temida AnsiString . En lugar de almacenar el texto en una AnsiString , guárdelo en TBytes . Ya tiene estructuras de datos que hacen un seguimiento de la codificación, así que ¿por qué no mantenerlas? Reemplace el registro que contiene la página de códigos y AnsiString con una página de códigos que contiene y TBytes . Entonces no tendrías miedo de que nada recodificara tu texto a tus espaldas. Y su código estará listo para usar en los compiladores móviles.


En este caso particular, usar RawByteString es una solución adecuada:

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 esta manera, RawByteString contiene la página de códigos, y la asignación de RawByteString a cualquier otro tipo de cadena, ya sea AnsiString o UTF8String o lo que sea, permitirá a RTL convertir automáticamente los datos de RawByteString de su página de códigos actual a la página de códigos de la cadena de destino (que incluye conversiones a UnicodeString ).

Si absolutamente debe devolver un AnsiString (que no recomiendo), aún puede usar SetCodePage() través de un encasillado:

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;

El reverso es mucho más fácil, solo use la página de códigos ya almacenada en una (Ansi|RawByte)String (solo asegúrese de que esas páginas de códigos siempre sean precisas), ya que RTL ya sabe cómo recuperar y usar la página de códigos por usted:

function StringToWideString(const Source: AnsiString): UnicodeString; begin Result := UnicodeString(Source); end;

function StringToWideString(const Source: RawByteString): UnicodeString; begin Result := UnicodeString(Source); end;

Dicho esto, sugeriría que se eliminen por completo las funciones de ayuda y simplemente se utilicen cadenas escritas. Deje que RTL maneje las conversiones por usted:

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;


Rebasando en System.pas , encontré la función SetAnsiString que hace lo que quiero:

procedure SetAnsiString(Dest: _PAnsiStr; Source: PWideChar; Length: Integer; CodePage: Word);

También es importante tener en cuenta que esta función inserta el CodePage en la estructura interna de StrRec para mí:

PStrRec(PByte(Dest) - SizeOf(StrRec)).codePage := CodePage;

Esto me permite escribir algo como:

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;

Así que cuando llamo:

actual := WideStringToString(''Ŧĥε qùíçķ ƀřǭŵņ fôx ǰűmpεď ōvêŗ ţħě łáƶÿ ďơǥ'', 850);

me sale el AnsiString resultante:

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 con la página de códigos apropiada ya rellenada en el miembro secreto de codePage .

La otra manera

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;

Nota : Cualquier código liberado en el dominio público. No se requiere atribución.