web-dev-qa-db-ja.com

UnicodeStringからAnsiStringへの変換

昔は、指定されたコードページのWideStringAnsiStringに変換する関数がありました。

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

そして、すべてがうまくいきました。関数にnicode文字列(つまり、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êŗ ţħě łáƶÿ ďơǥ_ (before)
  • _The qùíçk brown fôx jumped ovêr the lázÿ dog_ (after)

しかし、WindowsのWideChartoMultiByteは、最適なマッピングのかなり良い仕事をしています。それがするように設計されているように。

あとは

今、私たちは後の時代にいます。 WideStringは現在、Pariahであり、UnicodeStringが長所です。それは取るに足らない変更です。とにかく、Windows関数は一連のWideCharへのpointerのみを必要としたため(これは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 XE6では、AnsiStringにもコードページが密かに含まれています。

  • codePage:1258
  • 長さ:44
  • value:_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には、takes_Utf8String_という関数があります。既存のコードのどこかにUTF-8でエンコードされたAnsiStringを使用し、_Utf8ToWideString_を使用してUnicodeStringに変換しようとすると失敗します。

_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でエンコードされた文字列を1252であるかのようにUTF-8に変換します。

前進する方法

理想的には、私の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を返すのはどうでしょうか?

RawByteStringniversal recipient;であることを意図しているのは、私ではない多くの人々から繰り返し言われています。それは戻り値のパラメータとして意図されていませんでした:

_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には既にUnicodeStringRawByteString文字列に、またはその逆に変換できる関数が既にあります。

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

しかし、それは何ですか?

それとも他に何をすべきですか?

これは、些細な質問の背景の長いセットでした。 realの質問は、もちろん、代わりに何をすべきでしょうか? 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の残りの部分が整列します。

22
Ian Boyd

この特定のケースでは、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はコードページを保持し、RawByteStringAnsiStringまたは_UTF8String_などのその他の文字列型に割り当てると、 RawByteStringデータを現在のコードページから宛先文字列のコードページ(UnicodeStringへの変換を含む)に自動的に変換するRTL。

AnsiString(これはお勧めしません)を絶対に返す必要がある場合でも、型キャスト経由でSetCodePage()を使用できます。

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

RTLは既にコードページを取得して使用する方法を知っているため、_(Ansi|RawByte)String_に既に格納されているコードページを使用するだけで、逆の方がはるかに簡単です。

_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;
_
16
Remy Lebeau

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はクロスプラットフォームであるためにのみ存在するので、テキストがこれまでに作成されたことは驚くべきことです。


ただし、プログラムのAnsiString変数にANSIでエンコードされたテキストを保持しようとするのを間違えているのではないかと思います。通常、ANSIにエンコードするのはできるだけ遅く(相互運用境界で)、同様にできるだけ早くデコードします。

単純にこれを行う必要がある場合は、恐ろしいAnsiStringを完全に回避するより良い解決策があるかもしれません。テキストをAnsiStringに保存する代わりに、TBytesに保存します。エンコードを追跡するデータ構造が既にあるので、それらを保持しないでください。コードページとAnsiStringを含むレコードを、コードページとTBytesを含むレコードに置き換えます。そうすれば、あなたの背中の後ろにテキストを書き直すことを恐れることはないでしょう。また、モバイルコンパイラでコードを使用する準備が整います。

5
David Heffernan

System.pas、組み込み関数 SetAnsiString が見つかりました。

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

また、この関数がdoesCodePageを内部の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: $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' 

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;

:パブリックドメインにリリースされたコード。帰属は必要ありません。

3
Ian Boyd