如何使toLowerCase()和toUpperCase()在浏览器之间保持一致

时间:2018-11-26 19:48:29

标签: javascript unicode polyfills unicode-normalization case-folding

是否存在String.toLowerCase()和String.toUpperCase()的JavaScript polyfill实现,或JavaScript中可以与Unicode字符一起使用并且在浏览器之间保持一致的其他方法?

背景信息

在浏览器中甚至在不同版本的浏览器之间(例如FireFox 54与55)执行以下操作都会产生不同的结果:

document.write(String.fromCodePoint(223).normalize("NFKC").toLowerCase().toUpperCase().toLowerCase())

在Firefox 55中,它为您提供ss,在Firefox 54中,它为您提供ß

通常这很好,Locale之类的机制可以处理很多您想要的情况;但是,当您需要跨平台的一致行为时,例如与像这样的BaaS系统进行对话时,它可以大大简化您实际上在客户端上处理内部数据的交互。

1 个答案:

答案 0 :(得分:1)

请注意,此问题似乎只影响Firefox的过时版本,因此,除非您明确需要支持那些旧版本,否则可以选择根本不打扰。您的示例的行为在所有现代浏览器中都相同(因为Firefox中的更改)。可以验证using jsvu + eshost

$ jsvu # Update installed JavaScript engine binaries to the latest version.

$ eshost -e '"\xDF".normalize("NFKC").toLowerCase().toUpperCase().toLowerCase()'
#### Chakra
ss

#### V8 --harmony
ss

#### JavaScriptCore
ss

#### V8
ss

#### SpiderMonkey
ss

#### xs
ss

但是您问如何解决此问题,所以让我们继续。

https://tc39.github.io/ecma262/#sec-string.prototype.tolowercase的第4步指出:

  

根据Unicode默认大小写转换算法,将cuList设为一个列表,其中元素是toLowercase(cpList)的结果。

此{em> Unicode默认大小写转换算法在section 3.13 Default Case Algorithms of the Unicode standard中指定。

  

通过使用来自SpecialCasing.txt的映射加上来自UnicodeData.txt的映射来获得Unicode字符的完整大小写映射,不包括任何可能会冲突的映射。在这些文件中没有映射的任何字符都被认为是映射到自身。

     

[…]

     

以下规则指定Unicode字符串的默认大小写转换操作。这些规则使用全大小写转换操作Uppercase_Mapping(C)Lowercase_Mapping(C)Titlecase_Mapping(C),以及基于表述上下文的依赖于上下文的映射,如表3-17所示。

     

对于字符串X

     
      
  • R1 toUppercase(X):将C中的每个字符X映射到Uppercase_Mapping(C)
  •   
  • R2 toLowercase(X):将C中的每个字符X映射到Lowercase_Mapping(C)
  •   

下面是SpecialCasing.txt的示例,下面添加了我的注释:

00DF  ; 00DF   ; 0053 0073; 0053 0053;                      # LATIN SMALL LETTER SHARP S
<code>; <lower>; <title>  ; <upper>  ; (<condition_list>;)? # <comment>

此行说U + 00DF('ß')小写为U + 00DF(ß),大写字母为U + 0053 U + 0053(SS)。

下面是UnicodeData.txt的示例,下面添加了我的注释:

0041  ; LATIN CAPITAL LETTER A; Lu;0;L;;;;;N;;;; 0061   ;
<code>; <name>                ; <ignore>       ; <lower>; <upper>

此行说U + 0041('A')小写到U + 0061('a')。它没有显式的大写映射,这意味着它本身是大写的。

这里是UnicodeData.txt的另一个示例:

0061  ; LATIN SMALL LETTER A; Ll;0;L;;;;;N;; ;0041;        ; 0041
<code>; <name>              ; <ignore>            ; <lower>; <upper>

此行说U + 0061('a')大写到U + 0041('A')。它没有显式的小写映射,这意味着它本身是小写的。

您可以编写一个脚本来解析这两个文件,按照这些示例读取每一行,并构建小写/大写映射。然后,您可以将这些映射变成一个小的JavaScript库,该库提供符合规范的toLowerCase / toUpperCase功能。

这似乎是很多工作。根据Firefox中的旧行为以及确切的更改(?),您可以将工作限制为SpecialCasing.txt中的特殊映射。 (我假设您根据您提供的示例在Firefox 55中仅更改了特殊大小写。)

// Instead of…
function normalize(string) {
  const normalized = string.normalize('NFKC');
  const lowercased = normalized.toLowerCase();
  return lowercased;
}

// …one could do something like:
function lowerCaseSpecialCases(string) {
  // TODO: replace all SpecialCasing.txt characters with their lowercase
  // mapping.
  return string.replace(/TODO/g, fn);
}
function normalize(string) {
  const normalized = string.normalize('NFKC');
  const fixed = lowerCaseSpecialCases(normalized); // Workaround for old Firefox 54 behavior.
  const lowercased = fixed.toLowerCase();
  return lowercased;
}

我写了一个脚本,用于解析SpecialCasing.txt并生成一个JS库,该库实现上述lowerCaseSpecialCasestoLower的{​​{1}}功能。这里是:https://gist.github.com/mathiasbynens/a37e3f3138069729aa434ea90eea4a3c根据您的实际用例,您可能根本不需要toUpper及其对应的正则表达式和映射。这是完整的生成库:

toUpper