Sono andato avanti con la soluzione suggerita nella modifica.
Impossibile trovare un elenco di intervalli validi valido nello spazio Unicode; anche il database dei caratteri Unicode ufficiale avrebbe richiesto più analisi di quello che volevo veramente affrontare. Così, invece, ho scritto uno script veloce per eseguire il loop su ogni numero nell'intervallo [0x0, 0x10FFFF], convertirlo in un string
utilizzando Encoding.UTF32.GetString(BitConverter.GetBytes(code))
e provare il risultato con .Normalize()
. Se viene sollevata un'eccezione, quel valore non è un punto di codice valido.
Da questi risultati, ho creato la seguente funzione:
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);
}
Si noti che questa funzione non è necessariamente grande per general-purpose di pulizia, a seconda delle esigenze. Non esclude punti codice non assegnati o riservati, solo quelli che sono specificatamente designati come "non caratteri" (modifica: e alcuni altri che Normalize() sembra soffocare, come 0xfffff). Tuttavia, questi sembrano essere gli unici punti di codice che causeranno IsNormalized()
e Normalize()
per sollevare un'eccezione, quindi va bene per i miei scopi.
Dopodiché, si tratta solo di convertire la stringa in UTF-32 e di sfogliarla. Dal momento che Encoding.GetBytes()
restituisce un array di byte e IsValidCodePoint()
aspetta un'UInt32, ho usato un blocco non sicuro e un po 'di fusione per colmare il gap:
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);
}
Le prestazioni sono buone, relativamente parlando - di diversi ordini di grandezza più veloce rispetto al campione registrato nel domanda. Lasciare i dati in UTF-16 sarebbe presumibilmente più veloce e più efficiente in termini di memoria, ma al costo di un sacco di codice aggiuntivo per gestire i surrogati. E ovviamente avere replacement
un char
significa che il carattere di sostituzione deve essere sul BMP.
edit: Ecco una versione molto più conciso di IsValidCodePoint():
private static bool IsValidCodePoint(UInt32 point)
{
return point < 0xfdd0
|| (point >= 0xfdf0
&& ((point & 0xffff) != 0xffff)
&& ((point & 0xfffe) != 0xfffe)
&& point <= 0x10ffff
);
}
Si noti che, a causa delle coppie di surrogati, non sarà possibile guardare semplicemente a un 'DWORD' arbitrario e indica se si tratta di un punto di codice valido. –
UTF-32 non usa coppie surrogate. –
Come stai ricevendo questi dati errati? Se lo stai leggendo con la classe 'Encoding', questi caratteri dovrebbero essere rimossi di default. – porges