javascript и манипулирование строками с суррогатными парами utf-16 - PullRequest
16 голосов
/ 31 июля 2011

Я работаю над приложением для твиттера и просто наткнулся на мир 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

Твиттер видит и считает оба из них как 10 символов в длину.

Ответы [ 5 ]

21 голосов
/ 31 июля 2011

Javascript использует UCS-2 для внутреннего использования, который не является UTF-16. Из-за этого очень трудно обрабатывать Unicode в Javascript, и я не предлагаю делать это.

Что касается того, что делает Твиттер, вы, похоже, говорите, что он разумно считает по кодам, а не безумно по кодам.

Если у вас нет выбора, вам следует использовать язык программирования, который фактически поддерживает Unicode и который имеет интерфейс кодовой точки, а не интерфейс кодовой единицы. Как вы обнаружили, Javascript недостаточно хорош для этого.

Имеет Проклятие UCS-2, , которое даже хуже, чем Проклятие UTF-16, , которое уже достаточно плохо. Я говорю обо всем этом в сообщении OSCON, Sho Перестрелка поддержки Юникода: ? Хорошее, плохое и (в основном) Гадкий ? .

Из-за его ужасного проклятия, вы должны вручную моделировать UTF-16 с UCS-2 в Javascript, который просто сумасшедший.

Javascript также страдает от всевозможных других ужасных проблем с Unicode. Он не поддерживает графемы, нормализацию или сопоставление, все, что вам действительно нужно. И его регулярные выражения нарушены, иногда из-за Проклятия, иногда просто потому, что люди ошиблись. Например, Javascript неспособен к выражению регулярных выражений типа [?-?]. Javascript даже не поддерживает регистр, поэтому вы не можете написать шаблон, например /ΣΤΙΓΜΑΣ/i, и правильно его сопоставить στιγμας .

Вы можете попробовать использовать плагин XRegEXp , но вы не будете изгонять проклятие таким образом. Это может сделать только переход на язык с поддержкой Unicode, и он не является одним из них.

8 голосов
/ 31 июля 2011

Я собрал начальную точку для объекта обработки строки Unicode. Он создает функцию с именем UnicodeString(), которая принимает либо строку JavaScript, либо массив целых чисел, представляющих кодовые точки Unicode, и предоставляет свойства length и codePoints и методы toString() и 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("f??bar");
document.getElementById("output").textContent = "String: '" + ustr + "', length: " + ustr.length + ", slice(2, 4): " + ustr.slice(2, 4);
<div id="output"></div>
5 голосов
/ 28 мая 2012

Вот пара сценариев, которые могут быть полезны при работе с суррогатными парами в JavaScript:

4 голосов
/ 05 июня 2016

Итераторы строк Javascript могут дать вам действительные символы вместо суррогатных кодовых точек:

>>> [..."0123456789"]
["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
>>> [..."??????????"]
["?", "?", "?", "?", "?", "?", "?", "?", "?", "?"]
>>> [..."0123456789"].length
10
>>> [..."??????????"].length
10
3 голосов
/ 31 июля 2011

Это соответствует тому, что я искал. Требуется лучшая поддержка различных строковых функций. По мере добавления к нему я буду обновлять этот ответ.

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
...