2009-03-26 23 views
18

Tôi có một danh sách các giới hạn phạm vi ký tự mà tôi cần phải kiểm tra chuỗi, nhưng loại char trong .NET là UTF-16 và do đó một số ký tự trở thành cặp thay thế (thay thế). Vì vậy, khi liệt kê tất cả các số char trong một string, tôi không nhận được các điểm mã Unicode 32 bit và một số so sánh với giá trị cao không thành công.Làm thế nào bạn sẽ nhận được một mảng các điểm mã Unicode từ một chuỗi .NET?

Tôi hiểu Unicode đủ rõ ràng để tôi có thể phân tích cú pháp các byte nếu cần, nhưng tôi đang tìm giải pháp BCL C#/.NET Framework. Vì vậy ...

Làm cách nào bạn chuyển đổi một string thành mảng (int[]) của các điểm mã Unicode 32 bit?

Trả lời

9

Câu trả lời này không chính xác. Xem câu trả lời của @ Virtlink cho câu trả lời đúng.

static int[] ExtractScalars(string s) 
{ 
    if (!s.IsNormalized()) 
    { 
    s = s.Normalize(); 
    } 

    List<int> chars = new List<int>((s.Length * 3)/2); 

    var ee = StringInfo.GetTextElementEnumerator(s); 

    while (ee.MoveNext()) 
    { 
    string e = ee.GetTextElement(); 
    chars.Add(char.ConvertToUtf32(e, 0)); 
    } 

    return chars.ToArray(); 
} 

Ghi chú: Normalization là cần thiết để đối phó với các nhân vật composite.

+2

▼: Giải pháp của bạn loại bỏ bất kỳ bộ sửa đổi nào cters, và bạn đang xử lý _text elements_ và không phải là _code points_. Ví dụ, kết quả của 'ExtractScalars (" El Ni \ u006E \ u0303o ")' được chuyển đổi thành một chuỗi sẽ là '" El Nino "' thay vì '" El Niño "'. – Virtlink

+0

@Virtlink: Thú vị. Từ các tài liệu, nó phải có âm thanh như 'char.ConvertToUtf32 (string, int)' nên xử lý nó. Chỉnh sửa: Các tài liệu chết tiệt tuyên bố nó nên! https://msdn.microsoft.com/en-us/library/z2ys180b(v=vs.110).aspx – leppie

+0

@Virtlink: Ok, nó không xử lý các ký tự kết hợp, nhưng cho các cặp thay thế. – leppie

16

Bạn đang hỏi về điểm mã. Trong UTF-16 (C# 's char) chỉ có hai khả năng:

  1. Nhân vật là từ Basic Multilingual Plane, và được mã hóa bởi một đơn vị mã duy nhất.
  2. Nhân vật nằm ngoài BMP, và mã hóa sử dụng một cặp cao-thấp surrogare của đơn vị mã

Vì vậy, giả sử chuỗi là hợp lệ, này trả về một mảng mã điểm cho một định chuỗi:

public static int[] ToCodePoints(string str) 
{ 
    if (str == null) 
     throw new ArgumentNullException("str"); 

    var codePoints = new List<int>(str.Length); 
    for (int i = 0; i < str.Length; i++) 
    { 
     codePoints.Add(Char.ConvertToUtf32(str, i)); 
     if (Char.IsHighSurrogate(str[i])) 
      i += 1; 
    } 

    return codePoints.ToArray(); 
} 

một ví dụ với một cặp thay thế và một nhân vật sáng tác ñ:

ToCodePoints("\U0001F300 El Ni\u006E\u0303o");      // El Niño 
// { 0x1f300, 0x20, 0x45, 0x6c, 0x20, 0x4e, 0x69, 0x6e, 0x303, 0x6f } // E l N i n ̃◌ o 

Đây là một ví dụ khác. Hai điểm mã đại diện cho một lưu ý 32th âm nhạc với một giọng ngắt, cả hai cặp thay thế:

ToCodePoints("\U0001D162\U0001D181");    // 
// { 0x1d162, 0x1d181 }       // ◌ 

Khi C-normalized, họ được chia ra thành một Notehead, kết hợp gốc, kết hợp cờ và kết hợp giọng-ngắt, tất cả các cặp thay thế:

ToCodePoints("\U0001D162\U0001D181".Normalize()); // 
// { 0x1d158, 0x1d165, 0x1d170, 0x1d181 }   // ◌ 

Lưu ý rằng leppie's solution là không đúng. Câu hỏi là về các điểm mã, không phải yếu tố văn bản. Phần tử văn bản là sự kết hợp các điểm mã với nhau tạo thành một biểu đồ đơn. Ví dụ: trong ví dụ trên, số ñ trong chuỗi được thể hiện bằng chữ thường Latinh n theo sau là dấu ngã kết hợp ̃◌. Giải pháp của Leppie loại bỏ bất kỳ ký tự kết hợp nào không thể được chuẩn hóa thành một điểm mã duy nhất.

+1

Tôi muốn sử dụng 'var codePoint = Char.ConvertToUtf32 (...); if (codePoint> 0xFFFF) i ++; 'thay vì' Char.IsHighSurrogate'. – CodesInChaos

+0

@CodesInChaos: Tôi tin rằng điều đó sẽ tương đương. Nếu và chỉ khi char đầu tiên là một thay thế cao, bạn có thể có được một điểm mã trên '0xFFFF', nhưng hãy cho tôi biết nếu tôi nhầm. – Virtlink

+0

Tương đương. Đó chỉ là một gợi ý phong cách. – CodesInChaos

3

Không có vẻ như nó phải là phức tạp hơn này:

public static IEnumerable<int> Utf32CodePoints(this IEnumerable<char> s) 
{ 
    bool  useBigEndian = !BitConverter.IsLittleEndian; 
    Encoding utf32  = new UTF32Encoding(useBigEndian , false , true) ; 
    byte[] octets  = utf32.GetBytes(s) ; 

    for (int i = 0 ; i < octets.Length ; i+=4) 
    { 
    int codePoint = BitConverter.ToInt32(octets,i); 
    yield return codePoint; 
    } 

} 
+0

'BitConverter' sử dụng tính xác thực gốc,' Encoding.UTF32' sử dụng ít kết thúc. Vì vậy, điều này sẽ phá vỡ trên một hệ thống lớn endian. – CodesInChaos

+1

Tôi chỉ muốn nói rằng tôi đã đăng cùng một giải pháp (hầu như) làm nhận xét cho câu trả lời của leppie, _six seconds_ trước khi bạn gửi câu trả lời. Và cũng đề cập đến vấn đề về endianness. –

+0

@JeppeStigNielsen: Rõ ràng, những suy nghĩ tuyệt vời nghĩ như nhau :) –

0

tôi đã đưa ra những đề xuất bởi same approach Nicholas (và Jeppe), chỉ ngắn hơn:

public static IEnumerable<int> GetCodePoints(this string s) { 
     var utf32 = new UTF32Encoding(!BitConverter.IsLittleEndian, false, true); 
     var bytes = utf32.GetBytes(s); 
     return Enumerable.Range(0, bytes.Length/4).Select(i => BitConverter.ToInt32(bytes, i * 4)); 
    } 

Các liệt kê là tất cả những gì tôi cần, nhưng việc nhận được một mảng là tầm thường:

int[] codePoints = myString.GetCodePoints().ToArray(); 
Các vấn đề liên quan