如何从JavaScript字符串中删除无效的UTF-8字符?

31
我想从 JavaScript 字符串中删除所有无效的 UTF-8 字符。我已经试过以下 JavaScript 代码:
```javascript strTest = strTest.replace(/([\x00-\x7F]|[\xC0-\xDF][\x80-\xBF]|[\xE0-\xEF][\x80-\xBF]{2}|[\xF0-\xF7][\x80-\xBF]{3})|./g, "$1"); ```
看起来 UTF-8 验证正则表达式描述在这里是更完整的,我用同样的方式进行了适应:
```javascript strTest = strTest.replace(/([\x09\x0A\x0D\x20-\x7E]|[\xC2-\xDF][\x80-\xBF]|\xE0[\xA0-\xBF][\x80-\xBF]|[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}|\xED[\x80-\x9F][\x80-\xBF]|\xF0[\x90-\xBF][\x80-\xBF]{2}|[\xF1-\xF3][\x80-\xBF]{3}|\xF4[\x80-\x8F][\x80-\xBF]{2})|./g, "$1"); ```
这两个代码片段似乎都允许有效的 UTF-8 字符通过,但却几乎没有过滤出测试数据中的任何坏的 UTF-8 字符:UTF-8 解码器能力和压力测试。要么坏字符保持不变,要么似乎删除了一些字节,从而创建了一个新的无效字符。
我对 UTF-8 标准或 JavaScript 中的多字节并不很熟悉,因此我不确定是未能在正则表达式中表示正确的 UTF-8,还是在 JavaScript 中错误地应用了该正则表达式。
编辑:根据 Tomalak 的评论添加了全局标志到我的正则表达式 - 然而这对我仍然没有起作用。根据 bobince 的评论,我放弃在客户端执行此操作。

缺失的链接:链接1 - https://dev59.com/V3M_5IYBdhLWcg3wZSE6链接2 - http://www.w3.org/International/questions/qa-forms-utf-8 - Matthew Sielski
8个回答

41

我使用这种简单而坚固的方法:

function cleanString(input) {
    var output = "";
    for (var i=0; i<input.length; i++) {
        if (input.charCodeAt(i) <= 127) {
            output += input.charAt(i);
        }
    }
    return output;
}

基本上你所需要的仅是ASCII字符0-127,因此只需逐个字符重新构建字符串。如果是好字符,请保留它;如果不是,请舍弃它。这种方法非常健壮,如果你的目标是进行清理,它足够快(事实上速度非常快)。


3
输出 += 输入.charCodeAt(i) <= 127 ? 输入.charAt(i) : ' '。意思是,如果输入的字符编码小于或等于127,则将该字符添加到输出字符串中;否则,将空格添加到输出字符串中。 - user40521
使用 Ramda 的一行代码:const cleanString = input => R.map(char => char.charCodeAt(0) <= 127 ? char : '', input).join(''); - Adam McCormick
1
不使用 Ramda 的一行代码:const cleanString = input => Array.of(input).map(char => char.charCodeAt(0) <= 127 ? char : '', input).join('') - docodemore
1
我不相信docodemore的版本能够正常工作,Array.of(input)会返回一个只有一个元素的数组。我认为你想要这个:const cleanString = input => input.split('').map(char => char.charCodeAt(0) <= 127 ? char : '').join('') - Robin Clowers
1
请参见 https://dev59.com/AnE85IYBdhLWcg3wqVb5#57593674 以获取法语、西班牙语和其他“拉丁”语言的翻译。 - O'Neill
这将删除所有非ASCII字符,而不是无效的UTF-8。例如,"ф"是一个完全有效的UTF-8字符串,但是此代码片段返回""。虽然我不确定在JavaScript中如何删除“无效的UTF-8字符”,因为JavaScript将字符串存储在UTF-16中。 - user3064538

23

JavaScript字符串本质上是Unicode编码的,它们保存的是字符序列而不是字节序列,因此一个字符串中不可能包含无效的字节序列。

(严格来说,实际上它们包含的是UTF-16代码单元序列,这与字符序列略有不同,但这可能不是您现在需要担心的事情。)

如果出于某些原因确实需要,您可以创建一个字符串来保存用作占位符的字符,例如使用字符U+0080('\x80')表示字节0x80。如果您使用UTF-8将字符编码为字节,然后错误地使用ISO-8859-1将其解码回字符,那么就会得到这样的结果。这种情况下,有一个特殊的JavaScript习惯用语:

var bytelike= unescape(encodeURIComponent(characters));

要将UTF-8伪字节序列转换回字符:

var characters= decodeURIComponent(escape(bytelike));

(值得注意的是,几乎只有在以下情况下才应该使用escape/unescape功能。这些函数在任何其他程序中的存在几乎总是一个错误。)

decodeURIComponent(escape(bytes)),因为它的行为类似于UTF-8解码器,在输入的代码单元序列不可接受为UTF-8字节时,会引发错误。

在JavaScript中很少需要像这样处理字节字符串。最好在客户端本地以Unicode形式进行工作。浏览器会负责将字符串编码为UTF-8并传输(在表单提交或XMLHttpRequest中)。


1
感谢您提供的信息丰富的答案--本质上,我正在做的事情很困难,因为我不应该这样做。我在后端遇到了一些特定字符的问题,需要在那里解决它。 - Matthew Sielski
字符串 "\uD800" 是无效的,会导致 encodeURIComponent 抛出异常。 - OrangeDog
@OrangeDog:是的,因为没有那个代码单元序列的UTF-8表示。 - bobince
说 JavaScript 字符串不可能包含无效的字节序列是一个很好的理论,这也是我所期望的...然而,我目前正在尝试修复一个由字符串(从 mongodb 返回)引起的 node 问题,该字符串包含无效的 UTF8 字符。因此,看来毕竟是有可能的 =] - taxilian
@bobince 关于你最后一句话,浏览器不会转换使用 setRequestHeader 手动设置的头部值,并且在给出非 UTF 值时会令人遗憾地崩溃。最好提前预料到它;) - Sebas
“escape”是一个已过时的函数,在所有现代浏览器中都不被支持。 - David Spector

15

像西班牙语和法语这样的语言有带重音的字符,如"é",其代码在160-255的范围内,请参见https://www.ascii.cl/htmlcodes.htm

function cleanString(input) {
    var output = "";
    for (var i=0; i<input.length; i++) {
        if (input.charCodeAt(i) <= 127 || input.charCodeAt(i) >= 160 && input.charCodeAt(i) <= 255) {
            output += input.charAt(i);
        }
    }
    return output;
}

12

简单的错误,却有巨大的影响:

strTest = strTest.replace(/your regex here/g, "$1");
// ----------------------------------------^

如果没有使用 "global" 标志,替换只会发生在第一个匹配项上。

附注:要删除任何不符合某种复杂条件的字符,比如落在某些Unicode字符范围内,您可以使用负向先行断言:

var re = /(?![\x00-\x7F]|[\xC0-\xDF][\x80-\xBF]|[\xE0-\xEF][\x80-\xBF]{2}|[\xF0-\xF7][\x80-\xBF]{3})./g;
strTest = strTest.replace(re, "")

re解释如下:

(?!      # 负向前瞻:一个位置,*不被以下任何允许的字符范围后跟*
  […]    #   以上任意字符范围
)        # 结束前瞻
.        # 匹配这个字符(仅在满足前置条件时匹配!)

注意:原文中的英文标点符号已经被我改为中文标点符号。

谢谢,那是我代码中的一个大缺陷。不幸的是,现在全局标志已经生效,我发布的两个正则表达式似乎过滤掉了所有非ASCII字符。"压力测试"数据的第一个测试是一些有效的UTF-8文本,但被剥离了,如果我从http://www.columbia.edu/kermit/utf8.html获取样本文本,除了ASCII字符以外的所有内容都被删除了。 - Matthew Sielski

11

如果您想从 JavaScript 字符串中删除“无效字符”- � -,则可以像这样摆脱它们:

myString = myString.replace(/\uFFFD/g, '')

2
我遇到了一个问题,数字图像的“拍摄日期”数据返回了一个非常奇怪的结果。我的情况确实是独特的 - 使用Windows脚本宿主(WSH)和Shell.Application ActiveX对象,可以获取文件夹的命名空间对象,并调用GetDetailsOf函数来获取经过操作系统解析后的exif数据。
在Windows Vista和7中,结果看起来像这样:?8/?27/?2011 ??11:45 PM。
所以我的方法如下:
将日期字符串分割成字符数组,然后通过循环删除问号字符。
代码如下:
var chars = date.split(''); //split into characters var clean = ""; for (var i = 0; i < chars.length; i++) { if (chars[i].charCodeAt(0) < 255) clean += chars[i]; }
最终得到的结果是一个不包含问号字符的字符串。
虽然我知道您选择了完全不同的解决方案,但我认为我应该发布我的解决方案,以防其他人在处理此问题时无法使用服务器端语言的方法。

0

我已经整合了上面提出的一些解决方案,以确保不出错

       var removeNonUtf8 = (characters) => {
            try {
                // ignore invalid char ranges
                var bytelike = unescape(encodeURIComponent(characters));
                characters = decodeURIComponent(escape(bytelike));
            } catch (error) { }
            // remove �
            characters = characters.replace(/\uFFFD/g, '');
            return characters;
        },

1
"unescape" 和 "escape" 不再建议使用,并且在未来的浏览器中可能不被支持。 - David Spector
谢谢,有关escapeunescape替代方案的好答案在这里https://dev59.com/718d5IYBdhLWcg3w41Yp#51175973。 - loretoparisi
“好答案”仅适用于邮件链接,而不适用于此处提出的任何问题,我认为。在StackOverflow上提供了替代功能,但没有经过测试,而且我还没有找到任何经过测试的函数。Unicode真的很难操作。 - David Spector
1
@DavidSpector "将来的浏览器可能不支持" - 这有点小题大做,但这完全是错误的。在Web开发中,“已弃用”有着不同的含义,因为向后兼容性保证非常强大。即使这些函数的使用量比较少,要移除它们也非常困难。即使规范人员希望这样做,浏览器开发人员也不会这样做,如果这是一个旧特性(例如with),即使规范人员希望这样做。"已弃用"只是意味着"使用这个不是一个好主意,未来的特性(例如严格模式)可能不兼容等等"。 - undefined

0

我使用@Ali的解决方案不仅清理了我的字符串,还用HTML替换了无效字符:

 cleanString(input) {
    var output = "";
    for (var i = 0; i < input.length; i++) {
      if (input.charCodeAt(i) <= 127) {
        output += input.charAt(i);
      } else {
        output += "&#" + input.charCodeAt(i) + ";";
      }
    }
    return output;
  }

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