我正在使用Twitter应用程序,只是偶然发现了utf-8(16)的世界。似乎大多数javascript字符串函数对代理对都是盲目的。我必须重新编码一些东西才能让它具有广泛的字符意识。
我有这个函数将字符串解析成数组,同时保留代理对。然后我将重新编码几个函数来处理数组而不是字符串。
function sortSurrogates(str){
var cp = []; // array to hold code points
while(str.length){ // loop till we've done the whole string
if(/[\uD800-\uDFFF]/.test(str.substr(0,1))){ // test the first character
// High surrogate found low surrogate follows
cp.push(str.substr(0,2)); // push the two onto array
str = str.substr(2); // clip the two off the string
}else{ // else BMP code point
cp.push(str.substr(0,1)); // push one onto array
str = str.substr(1); // clip one from string
}
} // loop
return cp; // return the array
}
我的问题是,我有什么比较简单的东西吗?我看到很多人重申javascript本身处理utf-16,但我的测试让我相信,这可能是数据格式,但功能还不知道。我错过了一些简单的东西吗?
编辑: 为了帮助说明这个问题:
var a = "0123456789"; // U+0030 - U+0039 2 bytes each
var b = ""; // U+1D7D8 - U+1D7E1 4 bytes each
alert(a.length); // javascript shows 10
alert(b.length); // javascript shows 20
Twitter看到并计算这两个长度为10个字符。
答案 0 :(得分:21)
Javascript在内部使用UCS-2,而不是UTF-16。因此,在Javascript中处理Unicode非常困难,我不建议尝试这样做。
至于Twitter的作用,你似乎在说代码单元并非疯狂地用代码点来计算。
除非你别无选择,否则你应该使用一种实际支持Unicode的编程语言,它具有代码点接口,而不是代码单元接口。正如你所发现的,Javascript还不够好。
它有 UCS-2诅咒,甚至比 UTF-16诅咒更糟糕,这已经够糟了。我在OSCON讲话中讨论了所有这些, Unicode Support Shootout: The Good, the Bad, & the (mostly) Ugly 。
由于它的可怕诅咒,你必须在Javascript中用UCS-2手工模拟UTF-16,这简直就是疯了。
Javascript也遭受各种其他可怕的Unicode问题。它不支持字形或标准化或校对,所有这些都是你真正需要的。它的正则表达式被打破了,有时候是因为诅咒,有时只是因为人们弄错了。例如,Javascript无法表达[-]
等正则表达式。 Javascript甚至不支持casefolding,因此你不能编写像/ΣΤΙΓΜΑΣ/i
这样的模式并且正确匹配στιγμας。
您可以尝试使用the XRegEXp plugin,但不会以这种方式消除诅咒。只有改为使用Unicode支持的语言才能做到这一点,而且不是其中之一。
答案 1 :(得分:8)
我把Unicode字符串处理对象的起点拼凑在了一起。它创建了一个名为UnicodeString()
的函数,它接受JavaScript字符串或表示Unicode代码点的整数数组,并提供length
和codePoints
属性以及toString()
和{{1} } 方法。添加正则表达式支持会非常复杂,但slice()
和indexOf()
(没有正则表达式支持)之类的东西应该很容易实现。
split()
var UnicodeString = (function() {
function surrogatePairToCodePoint(charCode1, charCode2) {
return ((charCode1 & 0x3FF) << 10) + (charCode2 & 0x3FF) + 0x10000;
}
function stringToCodePointArray(str) {
var codePoints = [], i = 0, charCode;
while (i < str.length) {
charCode = str.charCodeAt(i);
if ((charCode & 0xF800) == 0xD800) {
codePoints.push(surrogatePairToCodePoint(charCode, str.charCodeAt(++i)));
} else {
codePoints.push(charCode);
}
++i;
}
return codePoints;
}
function codePointArrayToString(codePoints) {
var stringParts = [];
for (var i = 0, len = codePoints.length, codePoint, offset, codePointCharCodes; i < len; ++i) {
codePoint = codePoints[i];
if (codePoint > 0xFFFF) {
offset = codePoint - 0x10000;
codePointCharCodes = [0xD800 + (offset >> 10), 0xDC00 + (offset & 0x3FF)];
} else {
codePointCharCodes = [codePoint];
}
stringParts.push(String.fromCharCode.apply(String, codePointCharCodes));
}
return stringParts.join("");
}
function UnicodeString(arg) {
if (this instanceof UnicodeString) {
this.codePoints = (typeof arg == "string") ? stringToCodePointArray(arg) : arg;
this.length = this.codePoints.length;
} else {
return new UnicodeString(arg);
}
}
UnicodeString.prototype = {
slice: function(start, end) {
return new UnicodeString(this.codePoints.slice(start, end));
},
toString: function() {
return codePointArrayToString(this.codePoints);
}
};
return UnicodeString;
})();
var ustr = UnicodeString("fbar");
document.getElementById("output").textContent = "String: '" + ustr + "', length: " + ustr.length + ", slice(2, 4): " + ustr.slice(2, 4);
答案 2 :(得分:5)
以下是一些在JavaScript中处理代理项对时可能有用的脚本:
ES6 Unicode shims for ES3+添加了ECMAScript 6中的String.fromCodePoint
和String.prototype.codePointAt
方法.ES3 / 5 fromCharCode
和charCodeAt
方法不考虑代理对,因此给出了错误的结果。
Full 21-bit Unicode code point matching in XRegExp with \u{10FFFF}
允许匹配XRegExp正则表达式中的任何单个代码点。
答案 3 :(得分:4)
Javascript字符串迭代器可以为您提供实际字符而不是代理代码点:
>>> [..."0123456789"]
["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
>>> [...""]
["", "", "", "", "", "", "", "", "", ""]
>>> [..."0123456789"].length
10
>>> [...""].length
10
答案 4 :(得分:3)
这个与我所寻找的一致。它需要更好地支持不同的字符串函数。当我添加它时,我将更新这个答案。
function wString(str){
var T = this; //makes 'this' visible in functions
T.cp = []; //code point array
T.length = 0; //length attribute
T.wString = true; // (item.wString) tests for wString object
//member functions
sortSurrogates = function(s){ //returns array of utf-16 code points
var chrs = [];
while(s.length){ // loop till we've done the whole string
if(/[\uD800-\uDFFF]/.test(s.substr(0,1))){ // test the first character
// High surrogate found low surrogate follows
chrs.push(s.substr(0,2)); // push the two onto array
s = s.substr(2); // clip the two off the string
}else{ // else BMP code point
chrs.push(s.substr(0,1)); // push one onto array
s = s.substr(1); // clip one from string
}
} // loop
return chrs;
};
//end member functions
//prototype functions
T.substr = function(start,len){
if(len){
return T.cp.slice(start,start+len).join('');
}else{
return T.cp.slice(start).join('');
}
};
T.substring = function(start,end){
return T.cp.slice(start,end).join('');
};
T.replace = function(target,str){
//allow wStrings as parameters
if(str.wString) str = str.cp.join('');
if(target.wString) target = target.cp.join('');
return T.toString().replace(target,str);
};
T.equals = function(s){
if(!s.wString){
s = sortSurrogates(s);
T.cp = s;
}else{
T.cp = s.cp;
}
T.length = T.cp.length;
};
T.toString = function(){return T.cp.join('');};
//end prototype functions
T.equals(str)
};
测试结果:
// plain string
var x = "0123456789";
alert(x); // 0123456789
alert(x.substr(4,5)) // 45678
alert(x.substring(2,4)) // 23
alert(x.replace("456","x")); // 0123x789
alert(x.length); // 10
// wString object
x = new wString("");
alert(x); //
alert(x.substr(4,5)) //
alert(x.substring(2,4)) //
alert(x.replace("","x")); // x
alert(x.length); // 10