2016-07-10 5 views
1

Моя программа C# получает некоторые кодированные данные UTF-8 и декодирует ее с помощью Encoding.UTF8.GetString(data). Когда программа, которая создает данные, получает символы вне BMP, она кодирует их как 2 суррогатных символа, каждый из которых кодируется как UTF-8 отдельно. В таких случаях моя программа не может их декодировать должным образом.Как декодировать суррогатные символы, закодированные как UTF8?

Как я могу декодировать такие данные на C#?

Пример:

static void Main(string[] args) 
{ 
    string orig = ""; 
    byte[] correctUTF8 = Encoding.UTF8.GetBytes(orig); // Simulate correct conversion using std::codecvt_utf8_utf16<wchar_t> 
    Console.WriteLine("correctUTF8: " + BitConverter.ToString(correctUTF8)); // F0-9F-8C-8E - that's what the C++ program should've produced 

    // Simulate bad conversion using std::codecvt_utf8<wchar_t> - that's what I get from the program 
    byte[] badUTF8 = new byte[] { 0xED, 0xA0, 0xBC, 0xED, 0xBC, 0x8E }; 
    string badString = Encoding.UTF8.GetString(badUTF8); // ���� (4 * U+FFFD 'REPLACMENT CHARACTER') 
    // How can I convert this? 
} 

Примечание: Программа кодирования написана на C++, и преобразует данные, используя std::codecvt_utf8<wchar_t> (код ниже). Как ответ ПитераДунихо правильно отмечает, он должен был использовать std::codecvt_utf8_utf16<wchar_t>. К сожалению, Я не управляю этой программой и не могу изменить ее поведение - обрабатывать свой неверный ввод.

std::wstring_convert<std::codecvt_utf8<wchar_t>> utf8Converter; 
std::string utf8str = utf8Converter.to_bytes(wstr); 
+2

Я получаю '0xD83C 0xDF0E' за символ, а не' 0xD83D 0xDF0E', как вы утверждаете. Кроме того, если я использую .NET для кодирования этого символа как UTF8, я получаю 'F0 9F 8C 8E', а не' ED A0 BC ED BC 8E', как вы утверждаете. Наконец, когда я декодирую 'F0 9F 8C 8E' обратно в строку C#, я получаю' '' ', с которого я начал, и это кодирует в UTF16 как исходный' 0xD83C 0xDF0E', как и ожидалось. Пожалуйста, предоставьте хороший [mcve], который надежно воспроизводит вашу проблему. На данный момент это выглядит не что иное, как проблема с преобразованием кода в UTF8 (который вообще не похож на C# ... похоже, это C++). –

+2

Суррогатные коды * не могут быть закодированы в UTF-8 (или любой UTF), поэтому 'Encoding.UTF8.GetString' корректно заменяет недопустимые байты на' U + FFFD'. Как вы выглядите [CESU-8] (http://www.unicode.org/reports/tr26/). –

+0

@PeterDuniho: Персонажи исправлены извините. Я добавил образец и пояснил, что я больше не контролирую производственную программу. – Jonathan

ответ

3

Это невозможно знать наверняка без хорошего Minimal, Complete, and Verifiable code example. Но мне кажется, что вы используете неправильный конвертер в C++.

std::codecvt_utf8<wchar_t> язык изменен из UCS-2, а не UTF-16. Эти два очень похожи, но UCS-2 не поддерживает суррогатные пары, которые потребуются для кодирования символа, который вы хотите кодировать.

Вместо этого, вы должны использовать std::codecvt_utf8_utf16<wchar_t>:

std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> utf8Converter; 
std::string utf8str = utf8Converter.to_bytes(wstr); 

Когда я использую этот конвертер, я получаю UTF-8 байт, необходимых: F0 9F 8C 8E. Разумеется, они правильно декодируются в .NET при интерпретации UTF-8.


Добавление:

Вопрос был обновлен, чтобы указать, что код кодирования не может быть изменен. Вы застряли в UCS-2, который был закодирован в недопустимый UTF8. Поскольку UTF8 недействителен, вам придется декодировать текст самостоятельно.

Я вижу пару разумных способов сделать это. Во-первых, напишите декодер, который не заботится о том, включает ли UTF8 недопустимые байтовые последовательности. Во-вторых, используйте конвертер C++ std::wstring_convert<std::codecvt_utf8<wchar_t>> для декодирования байтов для вас (например, напишите свой код приема на C++ или напишите C++ DLL, который вы можете вызвать из своего кода C#, чтобы выполнить эту работу).

Второй вариант в некотором смысле более надежный, то есть вы используете именно декодер, который создал плохие данные в первую очередь. С другой стороны, это может быть чрезмерным даже для создания DLL, но не стоит писать весь клиент на C++. Создавая DLL, даже используя C++/CLI, у вас все еще есть некоторые головные боли, позволяющие взаимодействию работать правильно, если вы уже не являетесь экспертом.

Я знаком, но вряд ли экспертом, с C++/CLI. Я гораздо лучше с C#, так вот некоторый код для первого варианта:

private const int _khighOffset = 0xD800 - (0x10000 >> 10); 

/// <summary> 
/// Decodes a nominally UTF8 byte sequence as UTF16. Ignores all data errors 
/// except those which prevent coherent interpretation of the input data. 
/// Input with invalid-but-decodable UTF8 sequences will be decoded without 
/// error, and may lead to invalid UTF16. 
/// </summary> 
/// <param name="bytes">The UTF8 byte sequence to decode</param> 
/// <returns>A string value representing the decoded UTF8</returns> 
/// <remarks> 
/// This method has not been thoroughly validated. It should be tested 
/// carefully with a broad range of inputs (the entire UTF16 code point 
/// range would not be unreasonable) before being used in any sort of 
/// production environment. 
/// </remarks> 
private static string DecodeUtf8WithOverlong(byte[] bytes) 
{ 
    List<char> result = new List<char>(); 
    int continuationCount = 0, continuationAccumulator = 0, highBase = 0; 
    char continuationBase = '\0'; 

    for (int i = 0; i < bytes.Length; i++) 
    { 
     byte b = bytes[i]; 

     if (b < 0x80) 
     { 
      result.Add((char)b); 
      continue; 
     } 

     if (b < 0xC0) 
     { 
      // Byte values in this range are used only as continuation bytes. 
      // If we aren't expecting any continuation bytes, then the input 
      // is invalid beyond repair. 
      if (continuationCount == 0) 
      { 
       throw new ArgumentException("invalid encoding"); 
      } 

      // Each continuation byte represents 6 bits of the actual 
      // character value 
      continuationAccumulator <<= 6; 
      continuationAccumulator |= (b - 0x80); 
      if (--continuationCount == 0) 
      { 
       continuationAccumulator += highBase; 

       if (continuationAccumulator > 0xffff) 
       { 
        // Code point requires more than 16 bits, so split into surrogate pair 
        char highSurrogate = (char)(_khighOffset + (continuationAccumulator >> 10)), 
         lowSurrogate = (char)(0xDC00 + (continuationAccumulator & 0x3FF)); 

        result.Add(highSurrogate); 
        result.Add(lowSurrogate); 
       } 
       else 
       { 
        result.Add((char)(continuationBase | continuationAccumulator)); 
       } 
       continuationAccumulator = 0; 
       continuationBase = '\0'; 
       highBase = 0; 
      } 
      continue; 
     } 

     if (b < 0xE0) 
     { 
      continuationCount = 1; 
      continuationBase = (char)((b - 0xC0) * 0x0040); 
      continue; 
     } 

     if (b < 0xF0) 
     { 
      continuationCount = 2; 
      continuationBase = (char)(b == 0xE0 ? 0x0800 : (b - 0xE0) * 0x1000); 
      continue; 
     } 

     if (b < 0xF8) 
     { 
      continuationCount = 3; 
      highBase = (b - 0xF0) * 0x00040000; 
      continue; 
     } 

     if (b < 0xFC) 
     { 
      continuationCount = 4; 
      highBase = (b - 0xF8) * 0x01000000; 
      continue; 
     } 

     if (b < 0xFE) 
     { 
      continuationCount = 5; 
      highBase = (b - 0xFC) * 0x40000000; 
      continue; 
     } 

     // byte values of 0xFE and 0xFF are invalid 
     throw new ArgumentException("invalid encoding"); 
    } 

    return new string(result.ToArray()); 
} 

Я тестировал его с земным характером и она отлично работает для этого. Он также правильно декодирует правильный UTF8 для этого символа (т. Е. F0 9F 8C 8E).Конечно, вы захотите протестировать его с полным набором данных, если вы собираетесь использовать этот код для декодирования всего вашего входа в UTF8.

+0

Спасибо, это действительно правильный код для продюсерской программы. К сожалению, я не контролирую его, поэтому я ищу исправление на стороне C# для компенсации этого поведения. – Jonathan

+0

См. Редактирование. Я не стал писать декодер на C++/CLI, потому что это займет у меня намного больше времени, и 95% времени я буду бороться с вещами, которые не имеют никакого отношения к реальному вопросу. :) –

+0

Спасибо, это ответ, который я искал, хотя я надеялся на готовый декодер в .NET framework или на хорошо известную библиотеку ... Я знаком с C++/CLI, но это потребует неоправданных инвестиций в нашу систему сборки, не говоря уже о дополнительной DLL. Ты, сэр, джентльмен, ученый и принц среди мужчин! – Jonathan

Смежные вопросы