匹配正则表达式中的 Unicode 字符

Matching Unicode characters in a regular expression

提问人:André 提问时间:8/14/2021 最后编辑:André 更新时间:8/23/2021 访问量:554

问:

我使用 HttpClient 类从网站检索字符串。Web 服务器以 UTF-8 编码发送它们。字符串具有形式,如果它们位于字符串的末尾,我想从它们中删除管道、空格和空格后面的字符。abc | a

sText = Regex.Replace (sText, @"\| .$", "");

按预期工作。现在,在某些情况下,管道和空格后面跟着另一个字符,例如笑脸。该字符串的形式为 。上面的正则表达式不起作用,我必须使用abc | 😉

sText = Regex.Replace (sText, @"\| ..$", "");

取而代之的是(两个点)。

我很确定这与编码有关,并且笑脸在 UTF-8 中使用比拉丁字符更多的字节这一事实 - 以及 c# 不知道编码的事实。笑脸只是一个字符,即使它使用更多的字节,所以在告诉 c# 正确的编码(或转换字符串)后,第一个正则表达式应该在这两种情况下都有效。

这怎么能做到?

C# 正则表达式 UTF-8

评论

0赞 Wiktor Stribiżew 8/14/2021
在 .NET 中将表情符号与正则表达式匹配存在相当大的问题,因为没有构造。您所能做的就是为任何表情符号或任何字节 () 定义正则表达式。或者,如果您知道字符串中没有出现哪种字符,并使用它来构建字符串模式的末尾,则可以解决它。\p{Emoji}.
1赞 JosefZ 8/14/2021
Wiktor @Magnetron 在他(不公平地投反对票)删除的答案中(几乎)是正确的。 应该在 is 中作为内部编码工作,并且 BMP 以上的所有字符始终是两个代理项......Regex.Replace(sText, @"\| (\p{Cs}{2}|.)$", "");.NETUTF-16
0赞 André 8/15/2021
笑脸只是一个例子。我想删除所有看起来像一个项目的东西(一个字符,一个数字,一个符号,..)。\p{Cs}{2} 可能太有限了。

答:

1赞 AndreyCh 8/23/2021 #1

就像评论中建议的那样,这个问题很难使用正则表达式解决。你所说的“看起来像一个项目”实际上是一个字素簇。相应的 .NET 术语是一个“文本元素”,可以使用 StringInfo.GetTextElementEnumerator 对其进行分析和循环访问。

基于文本元素的可能解决方案可能非常简单:我们只需要从输入字符串中提取最后 3 个文本元素,并确保它们引用管道、空格,最后一个可以是任意值。请在下面找到建议的方法实现。

void Main()
{
    var inputs = new[] {
        "abc | a",
        "abc | ab", // The only that shouldn't be trimmed
        "abc | 😉",
        "abc | " + "\uD83D\uDD75\u200D\u2642\uFE0F" // "man-detective" (on Windows)
    };
    
    foreach (var input in inputs)
    {
        var res = TrimTrailingTextElement(input);

        Console.WriteLine("Input : " + input);
        Console.WriteLine("Result: " + res);
        Console.WriteLine();
    }
}

string TrimTrailingTextElement(string input)
{
    // A circular buffer for storing the last 3 text elements
    var lastThreeElementIdxs = new int[3] { -1, -1, -1 };
    
    // Get enumerator of text elements in the input string
    var enumerator = StringInfo.GetTextElementEnumerator(input);

    // Iterate through the enitre input string,
    // at each step save to the buffer the current element index
    var i = -1;
    while (enumerator.MoveNext())
    {
        i = (i + 1) % 3;
        lastThreeElementIdxs[i] = enumerator.ElementIndex;
    }

    // The buffer index must be positive for a non-empty input
    if (i >= 0)
    {
        // Extract indexes of the last 3 elements
        // from the circular buffer
        var i1 = lastThreeElementIdxs[(i + 1) % 3];
        var i2 = lastThreeElementIdxs[(i + 2) % 3];
        var i3 = lastThreeElementIdxs[i];

        if (i1 >= 0 && i2 >= 0 && i3 >= 0 && // All 3 indexes must be initialized
            i3 - i2 == 1 && i2 - i1 == 1 &&  // The 1 and 2 elements must be 1 char long
            input[i1] == '|' &&              // The 1 element must be a pipe 
            input[i2] == ' ')                // The 2 element must be a space
        {
            return input.Substring(0, i1);
        }
    }
    
    return input;
}