Poszedłem naprzód z rozwiązaniem wskazanym w edycji.
Nie można znaleźć łatwej w użyciu listy prawidłowych zakresów w obszarze Unicode; nawet oficjalna baza danych znaków Unicode wymagała więcej analizowania niż naprawdę chciałem. Zamiast tego napisałem szybki skrypt, aby zapętlić każdą liczbę z zakresu [0x0, 0x10FFFF], przekonwertować ją na string
przy użyciu Encoding.UTF32.GetString(BitConverter.GetBytes(code))
i spróbować uzyskać wynik w postaci . Jeśli zostanie zgłoszony wyjątek, wówczas ta wartość nie jest poprawnym punktem kodowym.
Z tych wyników, stworzyłem następującą funkcję:
bool IsValidCodePoint(UInt32 point)
{
return (point >= 0x0 && point <= 0xfdcf)
|| (point >= 0xfdf0 && point <= 0xfffd)
|| (point >= 0x10000 && point <= 0x1fffd)
|| (point >= 0x20000 && point <= 0x2fffd)
|| (point >= 0x30000 && point <= 0x3fffd)
|| (point >= 0x40000 && point <= 0x4fffd)
|| (point >= 0x50000 && point <= 0x5fffd)
|| (point >= 0x60000 && point <= 0x6fffd)
|| (point >= 0x70000 && point <= 0x7fffd)
|| (point >= 0x80000 && point <= 0x8fffd)
|| (point >= 0x90000 && point <= 0x9fffd)
|| (point >= 0xa0000 && point <= 0xafffd)
|| (point >= 0xb0000 && point <= 0xbfffd)
|| (point >= 0xc0000 && point <= 0xcfffd)
|| (point >= 0xd0000 && point <= 0xdfffd)
|| (point >= 0xe0000 && point <= 0xefffd)
|| (point >= 0xf0000 && point <= 0xffffd)
|| (point >= 0x100000 && point <= 0x10fffd);
}
pamiętać, że ta funkcja nie jest koniecznie dobre dla czyszczenia ogólnego przeznaczenia, w zależności od potrzeb. Nie wyklucza nieprzypisanych ani zarezerwowanych punktów kodowych, tylko te, które są specjalnie oznaczone jako "nietypowe" (edycja: i niektóre inne, które Normalize() wydaje się dławić, takie jak 0xfffff). Jednak wydaje się, że są to jedyne punkty kodowe, które powodują, że IsNormalized()
i Normalize()
mogą zgłaszać wyjątki, więc jest to w porządku dla moich celów.
Później chodzi tylko o konwersję ciągu znaków na UTF-32 i przeczesywanie go. Od Encoding.GetBytes()
zwraca tablicę bajtów i IsValidCodePoint()
oczekuje UInt32 użyłem niebezpiecznego bloku i jakiś odlew w celu wypełnienia luki:
unsafe string ReplaceInvalidCodePoints(string aString, char replacement)
{
if (char.IsHighSurrogate(replacement) || char.IsLowSurrogate(replacement))
throw new ArgumentException("Replacement cannot be a surrogate", "replacement");
byte[] utf32String = Encoding.UTF32.GetBytes(aString);
fixed (byte* d = utf32String)
fixed (byte* s = Encoding.UTF32.GetBytes(new[] { replacement }))
{
var data = (UInt32*)d;
var substitute = *(UInt32*)s;
for(var p = data; p < data + ((utf32String.Length)/sizeof(UInt32)); p++)
{
if (!(IsValidCodePoint(*p))) *p = substitute;
}
}
return Encoding.UTF32.GetString(utf32String);
}
wydajność jest dobra, stosunkowo mówiąc - kilka rzędów wielkości szybciej niż próbki Użyto w pytanie. Pozostawienie danych w UTF-16 prawdopodobnie byłoby szybsze i bardziej wydajne pod względem pamięci, ale kosztem wielu dodatkowych kodów do czynienia z surogatami. I oczywiście o replacement
być char
oznacza, że zastępujący znak musi być na BMP.
edit: Oto znacznie bardziej zwięzły wersja IsValidCodePoint():
private static bool IsValidCodePoint(UInt32 point)
{
return point < 0xfdd0
|| (point >= 0xfdf0
&& ((point & 0xffff) != 0xffff)
&& ((point & 0xfffe) != 0xfffe)
&& point <= 0x10ffff
);
}
zauważyć, że z powodu zastępczych parach, to nie będzie możliwe, aby po prostu patrzeć przy dowolnym "DWORD" i powiedz, czy jest to prawidłowy punkt kodowy. –
UTF-32 nie używa par zastępczych. –
W jaki sposób otrzymujesz te złe dane? Jeśli czytasz to z klasą "Kodowanie", te znaki powinny być domyślnie usunięte. – porges