这些Unicode组合字符是什么意思?我们如何过滤它们?

93

这些最近出现在Facebook评论部分。

我们如何进行内容清洗?


5
你之前没有问过这个问题吗?(真诚的提问。) - Ry-
5
这绝对不是 ASCII。 - Chris Eberle
33
为什么要关闭投票?这是一个与编程有关的问题,因为我想知道如何对此类输入进行消毒,以便我的网站评论部分不会变成13岁孩子的游乐场... - XCS
22
“所以我的网站评论区不会成为13岁以下孩子的游乐场。”实际上,如果没有清理,一个发布这些字符的人可能会使上面的评论无法阅读,这对用户体验来说一点也不愉快。 - XCS
15
@pjotr 这绝对不是浏览器的 bug。如果你想让字符不溢出包含框,你可以通过 CSS(overflow:hidden;)来解决这个问题... - XCS
显示剩余11条评论
4个回答

82

这些Unicode字符是怎么回事?

这是一个带有一系列组合字符的字符。因为相关的组合字符想要放在基本字符上面,它们会叠起来(字面意思)。例如:

ก้้้้้้้้้้้้้้้้้้้้

...这是一个 ก(泰文字符 ko kai)(U+0E01),后面跟着20个泰文组合字符 mai thoU+0E49)。

我们该如何进行清理?

你可以预处理文本并限制可应用于单个字符的组合字符数量,但这样做可能得不偿失。你需要所有当前字符的数据表,以便知道它们是否是组合字符,以及你需要确保至少允许一些组合字符,因为有些语言是在单个基础字符上写有几个变音符号的。现在,如果你想将评论限制为拉丁字符集,那么这将是一个更容易的范围检查,但当然只有在你想将评论限制为仅涉及几种语言时才是一个选项。更多信息、代码表等请参考unicode.org

顺便说一下,如果你想知道某个字符是如何组成的,最近我在JSBin上编写了一个快速而简单的“Unicode Show Me”页面,与另一个问题相关。你只需将文本复制粘贴到文本区域中,它会显示文本由哪些代码点(~字符)组成,并提供类似上面链接的页面来描述每个字符。它仅适用于U+FFFF及以下范围内的代码点,因为它是用JavaScript编写的,要处理JavaScript中超过U+FFFF的字符,你需要做更多的工作(因为在JavaScript中,“字符”总是16位,这意味着对于某些语言,一个字符可能会分割成两个不同的JavaScript“字符”,而我没有考虑到),但对于大多数文本来说很方便...


1
你难道不会将相同的组合码点重复出现的删除,只保留一个吗?你何时需要将相同的码点多次组合到基础码点上呢? - Remy Lebeau
4
“你何时需要将相同的代码点多次组合到基本代码点上?”我不知道,我对其他语言的编写方式知之甚少,例如泰语。我不会感到惊讶,如果发现在某些语言中,多次使用相同的代码点是有效的。但这样做并不能减少复杂性;你仍然需要一个Unicode表来确定哪些是组合字符。 - T.J. Crowder
我已经让你的页面接受来自URL的Unicode字符串,例如http://jsbin.com/erajer/7/?%E0%B8%81%E0%B9%89%E0%B9%89%E0%B9%89%E0%B9%89%E0%B9%89%E0%B9%89%E0%B9%89%E0%B9%89%E0%B9%89%E0%B9%89%E0%B9%89%E0%B9%89%E0%B9%89%E0%B9%89%E0%B9%89%E0%B9%89%E0%B9%89。 - ubershmekel
2
JavaScript库可轻松从字符串中删除Unicode组合标记:http://mths.be/stripcombiningmarks - Mathias Bynens
JavaScript使用UTF-16编码,包括“代理对”。 - dolmen
@dolmen:UTF-16始终存在代理对的可能性。你的意思是JavaScript容忍无效序列,而UTF-16则不会容忍。 - T.J. Crowder

17

如果您使用支持Unicode的正则表达式引擎,那么清理此类字符串非常容易。例如,在Perl中,您可以像这样从每个(用户感知的)字符中删除除第一个组合标记以外的所有内容:

#!/usr/bin/perl
use strict;
use utf8;

binmode(STDOUT, ':utf8');

my $string = "กิิ ก้้ ก็็ ก็็ กิิ ก้้ ก็็ กิิ ก้้ กิิ ก้้ ก็็ ก็็ กิิ ก้้ ก็็ กิิ ก้้";
$string =~ s/(\p{Mark})\p{Mark}+/$1/g; # Strip excess combining marks
print("$string\n");

这将打印:

กิ ก้ ก็ ก็ กิ ก้ ก็ กิ ก้ กิ ก้ ก็ ก็ กิ ก้ ก็ กิ ก้


9
我不懂藏文,但我担心这种暴力破解的方法可能会破坏语言的设计功能。我见过一些Unicode字符,它们有多个合法使用情况,如阿拉伯文。我会尝试记得向我的藏族同事咨询此事。 - FlipMcF
2
你说得对,确实有一些情况下多个组合标记是合法的。但是你可以轻松地更改正则表达式以允许一定数量的标记。 - nwellnhof
因为它回答了“你如何对此进行消毒”这个问题,所以我投了赞成票。但我认为这会成为一个维护噩梦。 - FlipMcF
此外,正则表达式只会删除_相邻_的重复字符。它不会清理例如:<base><macron><overline><macron><overline>...。因此,如果您的文本需要多个_不同的_组合字符,则可以通过;而恶意文本仍然可能被构建。 - Jesse Chisholm

14

“如何消毒这个”最好由T.J Crowder以上回答。

然而,我认为消毒不是正确的方法,Cristy 通过在包含元素的CSS上使用overflow:hidden 是正确的解决方案。

至少,这就是我解决它的方式。


7

好的,这个花了我一些时间才弄明白,我曾认为组合字符以产生zalgo只限于这些。因此,我期望下面的正则表达式能捕获这些怪物。

([\u0300–\u036F\u1AB0–\u1AFF\u1DC0–\u1DFF\u20D0–\u20FF\uFE20–\uFE2F]{2,})

但是,维基列表没有涵盖所有组合字符。

给我提示的是 "ก้้้้้้้้้้้้้้้้้้้้".charCodeAt(2).toString(16) = "e49",它不在组合字符范围内,而是属于“私人使用”。

在 C# 中,它们属于UnicodeCategory.NonSpacingMark,以下脚本可以将它们清除:

    [Test]
    public void IsZalgo()
    {
        var zalgo = new[] { UnicodeCategory.NonSpacingMark };

        File.Delete("IsModifyLike.html");
        File.AppendAllText("IsModifyLike.html", "<table>");
        for (var i = 0; i < 65535; i++)
        {
            var c = (char)i;
            if (zalgo.Contains(Char.GetUnicodeCategory(c)))
            {


                File.AppendAllText("IsModifyLike.html", string.Format("<tr><td>{0}</td><td>{1}</td><td>{2}</td><td>A&#{3};&#{3};&#{3}</td></tr>\n",  i.ToString("X"), c, Char.GetUnicodeCategory(c), i));

            }
        }
        File.AppendAllText("IsModifyLike.html", "</table>");
    }

通过查看生成的表格,您应该能够看到哪些是堆叠的。 维基上缺少的一个范围是06D6-06DC,另一个是0730-0749
更新: 这里是更新的正则表达式,可以捕获所有的zalgo,包括在“正常”范围内被绕过的那些。
([\u0300–\u036F\u1AB0–\u1AFF\u1DC0–\u1DFF\u20D0–\u20FF\uFE20–\uFE2F\u0483-\u0486\u05C7\u0610-\u061A\u0656-\u065F\u0670\u06D6-\u06ED\u0711\u0730-\u073F\u0743-\u074A\u0F18-\u0F19\u0F35\u0F37\u0F72-\u0F73\u0F7A-\u0F81\u0F84\u0e00-\u0eff\uFC5E-\uFC62]{2,})

最困难的部分是识别它们,一旦你做到了这一点 - 就有很多解决方案,包括上面提到的一些好方法。
希望这可以为您节省一些时间。

我会说,不要在这里发垃圾邮件! - Praveen Kumar Purushothaman
@PraveenKumar,您能详细说明您的意思吗? - Matas Vaitkevicius
我很感谢你的回答,但这是一个已经得到解答的问题。为什么要不必要地添加新的答案呢?这只是我的看法。此外,你的回答不是 JavaScript 的,对吧? - Praveen Kumar Purushothaman
5
它揭示了为什么正常的zalgo验证([\u0300–\u036F\u1AB0–\u1AFF\u1DC0–\u1DFF\u20D0–\u20FF\uFE20–\uFE2F]{2,})不起作用。你不觉得叠加Unicode不仅限于维基上的内容很有趣吗?你说的“失去回答的问题”是什么意思?编辑:你可能会觉得在一个三年前的问题中添加答案很奇怪,但由于我花了一些时间来弄清楚为什么这种类型的zalgo有效,我不能让这样的知识浪费掉。下一个人将节省一些时间。 - Matas Vaitkevicius
8
@PraveenKumar 这个问题没有说明语言,如果旧答案存在不足之处,发布新答案完全是合适的。不幸的是,我对这个问题没有足够的经验,否则我会投一个赞成票的。 - Mark Ransom
1
这个正则表达式的好处是可以捕获混合组合字符,缺点是永远不允许一个需要多个组合字符的基础。 - Jesse Chisholm

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接