C# 如何从字符串中删除无效的代码点?

C# 如何从字符串中删除无效的代码点?,c#,unicode,C#,Unicode,我有一个需要提供规范化字符串的例程。但是,传入的数据不一定是干净的,如果字符串包含无效的代码点,String.Normalize()将引发ArgumentException 我想做的就是用一个一次性的字符替换这些代码点,比如“?”。但要做到这一点,我首先需要一种有效的方法来搜索字符串以找到它们。这样做的好方法是什么 下面的代码可以工作,但它基本上使用try/catch作为原始的if语句,因此性能很差。我只是分享它来说明我所寻找的行为: private static string ReplaceI

我有一个需要提供规范化字符串的例程。但是,传入的数据不一定是干净的,如果字符串包含无效的代码点,String.Normalize()将引发ArgumentException

我想做的就是用一个一次性的字符替换这些代码点,比如“?”。但要做到这一点,我首先需要一种有效的方法来搜索字符串以找到它们。这样做的好方法是什么

下面的代码可以工作,但它基本上使用try/catch作为原始的if语句,因此性能很差。我只是分享它来说明我所寻找的行为:

private static string ReplaceInvalidCodePoints(string aString, string replacement)
{
    var builder = new StringBuilder(aString.Length);
    var enumerator = StringInfo.GetTextElementEnumerator(aString);

    while (enumerator.MoveNext())
    {
        string nextElement;
        try { nextElement = enumerator.GetTextElement().Normalize(); }
        catch (ArgumentException) { nextElement = replacement; }
        builder.Append(nextElement);
    }

    return builder.ToString();
}

(编辑:)我正在考虑将文本转换为UTF-32,这样我可以快速遍历它,看看每个dword是否对应一个有效的代码点。有没有一个功能可以做到这一点?如果没有,是否有一个浮动的无效范围列表?

应该包含您在参考C#中的有效/无效代码点列表时要查找的信息。至于如何做到这一点,我需要一点时间来制定一个正确的回答。不过,该链接应该可以帮助您入门。

我继续使用编辑中暗示的解决方案


我在Unicode空间中找不到易于使用的有效范围列表;即使是官方的Unicode字符数据库也需要进行比我真正想要处理的更多的解析。因此,我编写了一个快速脚本来循环[0x0,0x10FFFF]范围内的每个数字,使用
Encoding.UTF32.GetString(BitConverter.GetBytes(code))
将其转换为
字符串,然后尝试
.Normalize()。如果引发异常,则该值不是有效的代码点

根据这些结果,我创建了以下函数:

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);
}
相对而言,性能是好的——比问题中发布的样本快几个数量级。将数据保留在UTF-16中可能会更快、内存效率更高,但代价是处理代理的大量额外代码。当然,将
replacement
设置为
char
意味着替换字符必须位于BMP上

编辑:这里有一个更简洁的ISValidDepoint()版本:

private static bool IsValidCodePoint(UInt32点)
{
返回点<0xfdd0
||(点>=0xfdf0
&&((点&0xffff)!=0xffff)
&&((点&0xfffe)!=0xfffe)

&&点看起来唯一的方法是像您这样“手动”完成。这里的版本给出了与您相同的结果,但速度要快一点(大约是所有
字符串的4倍,最多
char.MaxValue
,在
U+10FFFF
之前改进较少)并且不需要
不安全的
代码。我还简化并注释了我的
IsCharacter
方法来解释每个选择:

static string ReplaceNonCharacters(string aString, char replacement)
{
    var sb = new StringBuilder(aString.Length);
    for (var i = 0; i < aString.Length; i++)
    {
        if (char.IsSurrogatePair(aString, i))
        {
            int c = char.ConvertToUtf32(aString, i);
            i++;
            if (IsCharacter(c))
                sb.Append(char.ConvertFromUtf32(c));
            else
                sb.Append(replacement);
        }
        else
        {
            char c = aString[i];
            if (IsCharacter(c))
                sb.Append(c);
            else
                sb.Append(replacement);
        }
    }
    return sb.ToString();
}

static bool IsCharacter(int point)
{
    return point < 0xFDD0 || // everything below here is fine
        point > 0xFDEF &&    // exclude the 0xFFD0...0xFDEF non-characters
        (point & 0xfffE) != 0xFFFE; // exclude all other non-characters
}
静态字符串替换非字符(字符串搜索、字符替换)
{
var sb=新的StringBuilder(收敛长度);
对于(变量i=0;i0xFDEF&&//排除0xFFD0…0xFDEF非字符
(点&0xfffE)!=0xfffE;//排除所有其他非字符
}

我最喜欢正则表达式方法

public static string StripInvalidUnicodeCharacters(string str)
{
    var invalidCharactersRegex = new Regex("([\ud800-\udbff](?![\udc00-\udfff]))|((?<![\ud800-\udbff])[\udc00-\udfff])");
    return invalidCharactersRegex.Replace(str, "");
}
公共静态字符串stripInvalidUnicode字符(字符串str)
{

var invalidCharactersRegex=新的正则表达式(([\ud800-\udbff](?![\udc00-\udfff]))|((?请注意,由于存在代理项对,因此无法简单地查看任意
DWORD
并判断它是否是有效的代码点。UTF-32不使用代理项对。您是如何接收这些错误数据的?如果您使用
编码
类读取这些数据,则默认情况下应删除这些字符。与@Porge相关问题是,是否是您的代码从源代码创建字符串(例如,文件、网络、大型数据库字段)这样就可以将逻辑进一步向下推,并在早期阶段以可能更好的吞吐量进行处理。谢谢。它已经尽可能向前推了,与框架接口相对应。这使得我无法完全控制字符串的来源,因此我无法逃避对可能糟糕的输入的计划。我不这么认为在这些文档中的任何地方都可以看到有效/无效代码点列表-您能给我们指一下吗?谢谢在页面顶部附近的“备注”下面,它写道:.NET Framework使用字符结构表示Unicode字符。Unicode标准使用一个称为代码点的唯一21位标量数标识每个Unicode字符,并定义UTF-16编码形式,该形式指定如何将代码点编码为一个或多个16位值的序列。每个16位值的范围为hexadecimal 0x0000到0xFFFF,存储在Char结构中。Char对象的值是其16位数字(序数)值。"好的,但是这里的问题是
String.Normalise
拒绝将范围0xfdd0 ef和0xfffe-f作为无效的代码点。这是我们想要的信息,我在
System.Char
页面上没有看到。对于未知字符,有一个指定的代码点,您应该用它替换,至少作为默认替换无论如何,您不需要不安全的代码;您可以使用
static string ReplaceNonCharacters(string aString, char replacement)
{
    var sb = new StringBuilder(aString.Length);
    for (var i = 0; i < aString.Length; i++)
    {
        if (char.IsSurrogatePair(aString, i))
        {
            int c = char.ConvertToUtf32(aString, i);
            i++;
            if (IsCharacter(c))
                sb.Append(char.ConvertFromUtf32(c));
            else
                sb.Append(replacement);
        }
        else
        {
            char c = aString[i];
            if (IsCharacter(c))
                sb.Append(c);
            else
                sb.Append(replacement);
        }
    }
    return sb.ToString();
}

static bool IsCharacter(int point)
{
    return point < 0xFDD0 || // everything below here is fine
        point > 0xFDEF &&    // exclude the 0xFFD0...0xFDEF non-characters
        (point & 0xfffE) != 0xFFFE; // exclude all other non-characters
}
public static string StripInvalidUnicodeCharacters(string str)
{
    var invalidCharactersRegex = new Regex("([\ud800-\udbff](?![\udc00-\udfff]))|((?<![\ud800-\udbff])[\udc00-\udfff])");
    return invalidCharactersRegex.Replace(str, "");
}