Convertir UnicodeString a AnsiString

En los viejos tiempos, tenía una función que convertiría unWideString a unaAnsiString 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 aUnicode cadena (es decir, datos codificados UTF-16) y lo convirtió en unAnsiString, con el entendimiento de que los bytes en elAnsiString caracteres representados de la página de códigos especificada.

Por ejemplo:

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

devolvería elWindows-1252 cadena codificada:

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

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

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

Pero las ventanasWideChartoMultiByte 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, conUnicodeString siendo la bondad Es un cambio intrascendente; ya que la función de Windows solo necesitaba unpuntero a una serie deWideChar de todos modos (que unUnicodeString también es). Entonces cambiamos la declaración para usarUnicodeString en lugar:

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

Ahora llegamos al valor de retorno. Yo tengo unAnsiString 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. Seguí el rastro de qué página de códigosAnsiString realmente contenido; tuve querecuerda que el regresóAnsiString no se codificó utilizando la configuración regional de la computadora (por ejemplo, Windows 1258), sino que se codificó utilizando otra página de códigos (elCodePage página de código).

Pero en Delphi XE6 unAnsiString también contiene secretamente la página de códigos:

página de código: 1258longitud: 44valor: The qùíçk brown fôx jumped ovêr the lázÿ dog

Esta página de códigos está mal. 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 elAnsiString estaba en una página de códigos particular, solo tenía que asegurarme de transmitir esa información.

Entonces, cuando quería decodificar la cadena, tenía que pasarle la página de códigos:

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

con

function StringToWideString(s: AnsiString; CodePage: UINT): UnicodeString;
begin
   ...
   MultiByteToWideChar(...);
   ...
end;
Entonces una persona arruina todo

El problema era que en los viejos tiempos declaraba un tipo llamadoUtf8String:

type
   Utf8String = type AnsiString;

Porque era lo suficientemente común como para 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 quetoma a Utf8String. Si algún código existente en algún lugar fuera un UTF-8 codificadoAnsiStringe intente convertirlo a UnicodeString usandoUtf8ToWideString 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 vuelveAnsiString(1252) (AnsiString etiquetado como codificado usando la página de códigos actual)el resultado devuelto se almacena en unAnsiString(65001) cuerda (Utf8String)Delphi convierte la cadena codificada UTF-8 en UTF-8 como si fuera 1252.Cómo avanzar

Idealmente miUnicodeStringToString(string, codePage) función (que devuelve unAnsiString) podría establecer elCodePage dentro de la cadena para que coincida con la página de códigos real usando algo comoSetCodePage:

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 manualmente andar con la estructura interna de unAnsiString Es horriblemente peligroso.

¿Y qué hay de volverRawByteString?

Se ha dicho, más de una vez, por muchas personas que no soy yo queRawByteString está destinado a ser elrecipiente 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 utilizar el soporte y el documento documentado.SetCodePage.

Pero si vamos a cruzar una línea y comenzar a regresarRawByteString, seguramente Delphi ya tiene una función que puede convertir unUnicodeString a unRawByteString cadena 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 qué más debo hacer?

Este era un conjunto de antecedentes de largo aliento para una pregunta trivial. losreal la pregunta es, por supuesto, ¿qué debería hacer en su lugar? Hay mucho código por ahí que depende deUnicodeStringToString y al revés.

tl; dr:

Puedo convertir unUnicodeString a UTF haciendo:

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

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

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

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

Mi sensación es que todo es realmente unAnsiString:

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

Debería morder la bala, abrir el bustoAnsiString estructura e inserte la página de códigos correcta en ella:

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 del VCL se alineará.