使用国际字符的JavaScript验证问题

24

在 Stack Overflow,我们使用优秀的jQuery验证插件来进行客户端输入验证,以确保提交到服务器之前的输入有效性。

一般而言,它工作得很好,但是有一个问题让我们感到困惑。

在提问/回答表单的用户名字段上使用以下验证器方法(请注意,您必须注销才能在实时网站上看到此字段;它位于每个/question页面和/ask页面上)

$.validator.addMethod("validUserName",
  function(value, element) {
  return this.optional(element) || 
  /^[\w\-\s\dÀÈÌÒÙàèìòùÁÉÍÓÚÝáéíóúýÂÊÎÔÛâêîôûÃÑÕãñõÄËÏÖÜäëïöüçÇßØøÅåÆæÞþÐð]+$/.test(value); },
  "Can only contain A-Z, 0-9, spaces, and hyphens.");  

现在这个正则表达式看起来很奇怪,但其实很简单:
- 匹配字符串开头 (^) - 匹配以下任意一个..
- 单词字符 (\w) - 短横线 (-) - 空格 (\s) - 数字 (\d) - 其他语言的字符 (àèìòù等)
- 匹配字符串结尾 ($)
是的,我们遇到了国际化正则表达式问题。JavaScript中"单词字符"的定义完全不包括国际字符。
奇怪的是,即使我们手动添加了大量有效的国际字符到正则表达式中,它也无法正常工作。你不能在用户名输入框中输入这些国际字符而不会得到验证错误:"只能包含A-Z、0-9、空格和短横线"。
显然,验证对于正则表达式的其他部分确实起作用,那么问题出在哪里呢?
另一个奇怪的地方是,这个验证在浏览器的JavaScript控制台中可以正常工作,但在我们标准的*.js文件中执行时却不行。
我们之前在JavaScript代码中遇到过一些非常奇怪的国际字符问题,导致出现了一些非常恶心的hack。我们希望能够理解这里发生了什么以及为什么会发生。请给我们一些启示!正则表达式如下: /^[\w-\sÀÈÌÒÙàèìòùÁÉÍÓÚÝáéíóúýÂÊÎÔÛâêîôûÃÑÕãñõÄËÏÖÜäëïöüçÇßØøÅåÆæÞþÐð]+$/ .test('ÓBill de hÓra') === true

这可能是一个字符编码问题吗?也就是说,用户输入的疯狂字符“Ä”在你的正则表达式中不是“Ä”吗? - balpha
我不知道答案,但那是一个很好的提问方式。 - Onorio Catenacci
@Onorio Jeff总是倡导提出写得好的问题,所以他自己最好也这样做 :-) 但你肯定是对的。 - balpha
é不是来自月球语言的字符,pokémon不也是英文字母吗?还请检查我的评论,Jorn的回答。 - Hoffmann
7个回答

36

我认为电子邮件和URL验证方法在这里是一个很好的参考,例如电子邮件方法:

email: function(value, element) {
    return this.optional(element) || /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$/i.test(value);
},

编译该正则表达式的脚本

换句话说,使用这个替换你的任意字符列表可能会有所帮助:

[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]

基本上,这个方法通过用更一般的定义替换需要编码的字符来避免其他地方出现的字符编码问题。虽然不一定更易读,但迄今为止它比你的完整列表更短。


只是为了澄清这个问题为什么有效。如果您的.js文件以字符编码进行编码,则其中的正则表达式内的所有字符都将用该编码表示,即使您的网页使用另一种编码也是如此。在我的项目中,我只需将可能包含国际字符串的所有内容都编码为UTF-8。这包括.js文件。杰夫可能遇到的问题是,他的.js文件是以一个字符集编码的,而他的页面则使用另一个字符集解析,他的HTTP请求/响应可能与页面使用相同的字符集进行编码。这就解释了为什么调试器可以工作。 - Hoffmann
另外一件事,尝试使用alert("áéíóú"),如果它正确显示,那么你的javascript文件与页面采用相同的编码方式。另一个解决方案是通过以下方式包含你的javascript文件:<script src="myscripts.js" charset="ISOsomething" />其中ISOsomething是你的.js文件的编码方式。这是一个常见的错误,因为大多数IDE默认使用的编码方式几乎从不是UTF-8。 - Hoffmann
答案中的两个链接都已失效。 - Mottie
这个对我很有帮助,支持i18n字符和不含双引号的正则表达式:^[a-zA-Z0-9!@#$%^~&*/?:',\|{}()-_+\s\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]*$`。 - STEEL

14

这绝对可以归因于编码问题。

是的,“ECMA不应该关心编码…”之类的废话,但是如果你使用的是火狐浏览器,可以在查看 > 字符编码 > 西方(ISO-8859-1),然后尝试使用名称字段。

在我手动更改编码后,它对我来说运行良好(尽管页面的其他部分可能不喜欢编码切换)。

(在IE8上,你可以转到页面 > 编码 > 西欧(Windows)以获得相同的效果)。


他是正确的,这神奇地使得名称验证起作用了(!) - Jeff Atwood

3

JS文件的字符编码是什么?

对于XML QNames,我使用这个RegExp:

/**
 * Definition of an XML Name
 */
var NameStartChar = "A-Za-z:_\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D"+
                    "\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF"+
                    "\uF900-\uFDCF\uFDF0-\uFFFD\u010000-\u0EFFFF";
var NameChar = NameStartChar+"\\-\\.0-9\u00B7\u0300-\u036F\u203F-\u2040";
var Name = "^["+NameStartChar+"]["+NameChar+"]*$";
RegExp (Name).test (value);

它能够轻松地处理国际化字符。请注意转义。由于这一点,我能够将JS文件限制在ASCII字符范围内。因此,在处理ISO-8859和UTF-8字符集时就不会出现问题。

然而,如果您使用的字符编码中不存在ASCII字符集(例如,亚洲的UTF-16),那么这一点就不再适用。

干杯!


据我所了解,验证器规则位于外部JS文件中。那么我打赌该文件的编码有误(即非UTF-8)。 - Boldewyn
我正在使用Notepad2打开磁盘上的文件,它看起来正确--与上面在ANSI编码下看到的完全相同,并且当我切换到Unicode、UTF-8编码时也是完全相同的。 - Jeff Atwood
不可能吧。 ANSI的'Ä'(==ISO-8859-1)具有单字节表示'C4',而UTF-8的'Ä'在十六进制编辑器中看起来像'C3 84'。你所说的'switch'是什么意思?它是真正的编码转换吗? - Boldewyn
奇怪的是,这在包含“<”的字符串上匹配为真。似乎是因为NameStartChar的最后一位“\u010000-\u0EFFFF”,即使“<”是\u003C而不在该范围内。同样的,@、?、=和其他字符在'9'和'A'之间。你对此有什么想法? - jwl
我创建了一个JavaScript库来完成一些工作,不确定它是否正确或最优,但请查看:http://code.google.com/p/charfunk/ - jwl
显示剩余2条评论

3
晚来一步,但我刚刚使用了这个表达式,它对我很有效。看起来相当全面和相对简单:

var re = /^[A-zÀ-Ÿ\s\d-]*$/g; 
var str1 = 'casa-me,pois 99 estou farto! Eis a lista:uma;duas;três';
var str2 = 'casa-me pois 99 estou farto Eis a lista uma duas três';
var str3 = 'àèìòùÀÈÌÒÙáéíóúýÁÉÍÓÚÝâêîôûÂÊÎÔÛãñõÃÑÕäëïöüÿÄËÏÖÜŸçÇßØøÅåÆæœ'

alert(re.test(str1));
alert(re.test(str2));
alert(re.test(str3));


2

列出的国际字符是扩展ASCII的一部分。您添加的那些字符则不是。


2

既然这个语句在控制台中可以运行,那么这是否与你的 .js 文件保存方式有关(即 ASCII 还是 UTF-8),并且浏览器正在以此方式加载它们并在过程中转换字符?


JS 对 UTF-8 一无所知,即使设置了编码也是如此。 - dusoft
但是浏览器会,不是吗?如果文件以UTF-8格式加载,并且浏览器的JS引擎因为浏览器错误地加载文件而错误地解释字符,那该怎么办? - Colin
2
是的,浏览器很在意。如果您将“Ä”保存为非Unicode格式,则会导致无效的UTF-8字节流。因此,它永远无法匹配与“Ä”对应的UTF-8字节流。 - Boldewyn
浏览器很在意浏览器,因此JS引擎也很在意。 - Boldewyn

2
使用类似Fiddler或Charles(不要使用Firefox的Net面板或任何其他实际位于浏览器内部的东西)来检查实际传输的内容。几乎可以肯定这是一个编码问题:文件可能已保存在某个微软字符集中,并以UTF-8格式发送,或者可能反过来。

对于JS RegExps,正如Boldewyn指出的那样,您可以通过指定超出US-ASCII范围的字符的Unicode代码点来避免这些问题。然而,最好确保您没有在保存文件的地方和提供服务的地方混淆编码。


通过网络传输gzip,这样做非常麻烦。 - Jeff Atwood
Fiddler和Charles都可以处理这个。如果我没记错,Fiddler(至少在版本2中)会在响应查看区域提供一个按钮,让您查看未压缩的内容。 - NickFitz

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