web-dev-qa-db-ja.com

Unicodeサロゲートペアをリテラル文字列に変換します

ある文字列から別の文字列に高Unicode文字を読み取ろうとしています。簡潔にするために、以下に示すようにコードを簡略化します。

public static void UnicodeTest()
{
    var highUnicodeChar = "????"; //Not the standard A

    var result1 = highUnicodeChar; //this works
    var result2 = highUnicodeChar[0].ToString(); // returns \ud835
}

highUnicodeCharresult1に直接割り当てると、リテラル値????が保持されます。インデックスでアクセスしようとすると、\ud835が返されます。私が理解しているように、これはUTF-32文字を表すために使用されるUTF-16文字の代理ペアです。この問題は、暗黙的にcharstringに変換しようとすることに関係していると確信しています。

結局、私はresult2result1と同じ値を生成することを望んでいます。これどうやってするの?

17
hargle

nicode には、 コードポイント があります。これらは21ビット長です。あなたのキャラクター ????、 - Mathematical Bold Capital A 、U + 1D400のコードポイントがあります。

Unicodeエンコーディングでは、コード単位があります。エンコーディングの自然単位は次のとおりです。 8ビット for TF-816ビット for TF-16 など。 1つ以上のコードユニットが単一のコードポイントをエンコードします。

UTF-16では、単一のコードポイントを形成する2つのコードユニットは、サロゲートペアと呼ばれます。サロゲートペアは、16ビットを超えるコードポイント(U + 10000以上)をエンコードするために使用されます。

.NET Charは単一のUTF-16コードユニットを表し、.NET Stringはコードユニットのコレクションであるため、これは.NETでは少し注意が必要です。

だからあなたのコードポイント???? (U + 1D400)は16ビットに収まらず、サロゲートペアが必要です。つまり、文字列には2つのコードユニットが含まれています。

var highUnicodeChar = "????";
char a = highUnicodeChar[0]; // code unit 0xD835
char b = highUnicodeChar[1]; // code unit 0xDC00

つまり、そのように文字列にインデックスを付けると、実際にはサロゲートペアの半分しか取得できません。

IsSurrogatePair を使用して、サロゲートペアをテストできます。例えば:

string GetFullCodePointAtIndex(string s, int idx) =>
    s.Substring(idx, char.IsSurrogatePair(s, idx) ? 2 : 1);

Unicodeでの可変エンコーディングのうさぎの穴はコードポイントで終わらないことに注意することが重要です。 書記素クラスターは、尋ねられたときにほとんどの人が最終的に「キャラクター」と呼ぶ「目に見えるもの」です。書記素クラスターは、1つ以上のコードポイント(基本文字と0個以上の結合文字)から作成されます。結合文字の例は、追加したいumlautまたはその他のさまざまな装飾/修飾子です。結合文字で何ができるかの恐ろしい例については、 この回答 を参照してください。

結合文字をテストするには、 GetUnicodeCategory を使用して、囲みマーク、非スペーシングマーク、またはスペーシングマークを確認できます。

25
Cory Nelson

highUnicodeChar文字列からユーザーの観点から最初の「アトミック」文字(つまり、最初のUnicode grapheme cluster )を抽出したいようです。ここで、「アトミック」文字には、 代理ペア

StringInfo.GetTextElementEnumerator() を使用してこれを行うことができ、stringをアトミックチャンクに分割してから、最初のチャンクを取得します。

まず、次の拡張メソッドを定義します。

_public static class TextExtensions
{
    public static IEnumerable<string> TextElements(this string s)
    {
        // StringInfo.GetTextElementEnumerator is a .Net 1.1 class that doesn't implement IEnumerable<string>, so convert
        if (s == null)
            yield break;
        var enumerator = StringInfo.GetTextElementEnumerator(s);
        while (enumerator.MoveNext())
            yield return enumerator.GetTextElement();
    }
}
_

今、あなたはすることができます:

_var result2 = highUnicodeChar.TextElements().FirstOrDefault() ?? "";
_

StringInfo.GetTextElementEnumerator()もUnicode 結合文字 をグループ化するため、文字列_Ĥ=T̂+V̂_の最初の書記素クラスターはHではなく__になることに注意してください。

サンプルフィドル ここ

8
dbc