Как декодировать суррогатные символы, закодированные как UTF8?

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

Как я могу декодировать такие данные в 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> (код ниже). Как правильно отмечает ответ @ PeterDuniho, он должен был использовать std::codecvt_utf8_utf16<wchar_t>, К несчастью, Я не контролирую эту программу и не могу изменить ее поведение — только обработаю ее искаженный ввод.

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

1

Решение

Невозможно знать наверняка без хорошего Пример минимального, полного и проверяемого кода. Но мне кажется, что вы используете неправильный конвертер в 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.

3

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]