Строки JavaScript за пределами BMP - PullRequest
38 голосов
/ 19 сентября 2010

BMP Базовая многоязычная плоскость

Согласно JavaScript: хорошие детали :

JavaScript был создан в то время, когда Unicode был 16-битным набором символов, поэтому все символы в JavaScript имеют ширину 16 бит.

Это наводит меня на мысль, что JavaScript использует UCS-2 (не UTF-16!) И может обрабатывать символы только до U + FFFF.

Дальнейшее расследование подтверждает это:

> String.fromCharCode(0x20001);

Кажется, что метод fromCharCode использует только младшие 16 бит при возврате символа Unicode. Попытка получить U + 20001 (унифицированный идеограф CJK 20001) вместо этого возвращает U + 0001.

Вопрос: возможно ли вообще обрабатывать символы после BMP в JavaScript?


2011-07-31: слайд двенадцать из Перестрелка с поддержкой Unicode: Хорошие, плохие и (в основном) уродливые довольно хорошо освещают вопросы, связанные с этим:

image

Ответы [ 5 ]

34 голосов
/ 21 сентября 2010

Зависит от того, что вы подразумеваете под «поддержкой».Конечно, вы можете поместить символы не-UCS-2 в строку JS, используя суррогаты, и браузеры отобразят их, если смогут.

Но каждый элемент в строке JS представляет собой отдельную кодовую единицу UTF-16.На уровне языка не поддерживается обработка полных символов: все стандартные члены String (length, split, slice и т. Д.) Имеют дело с единицами кода, а не с символами, поэтому вполне удачно разделят суррогатные пары или сохранят недопустимый суррогатпоследовательности.

Если вам нужны методы, поддерживающие суррогат, я боюсь, что вам придется начать писать их самостоятельно!Например:

String.prototype.getCodePointLength= function() {
    return this.length-this.split(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g).length+1;
};

String.fromCodePoint= function() {
    var chars= Array.prototype.slice.call(arguments);
    for (var i= chars.length; i-->0;) {
        var n = chars[i]-0x10000;
        if (n>=0)
            chars.splice(i, 1, 0xD800+(n>>10), 0xDC00+(n&0x3FF));
    }
    return String.fromCharCode.apply(null, chars);
};
2 голосов
/ 01 февраля 2013

Я пришел к тому же выводу, что и Бобинс.Если вы хотите работать со строками, содержащими символы Юникода за пределами BMP, вы должны переопределить методы String javascript.Это потому, что javascript считает символы как каждое 16-битное значение кода.Символы вне BMP должны быть представлены двумя значениями кода.Поэтому вы сталкиваетесь со случаем, когда некоторые символы считаются двумя символами, а некоторые - только одним.

Я переопределил следующие методы для обработки каждой кодовой точки Unicode как одного символа: .length, .charCodeAt,.fromCharCode, .charAt, .indexOf, .lastIndexOf, .splice и .split.

Вы можете проверить это на jsfiddle: http://jsfiddle.net/Y89Du/

Вот код без комментариев.Я проверил это, но он все еще может иметь ошибки.Комментарии приветствуются.

if (!String.prototype.ucLength) {
    String.prototype.ucLength = function() {
        // this solution was taken from 
        // /3101240/stroki-javascript-za-predelami-bmp
        return this.length - this.split(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g).length + 1;
    };
}

if (!String.prototype.codePointAt) {
    String.prototype.codePointAt = function (ucPos) {
        if (isNaN(ucPos)){
            ucPos = 0;
        }
        var str = String(this);
        var codePoint = null;
        var pairFound = false;
        var ucIndex = -1;
        var i = 0;  
        while (i < str.length){
            ucIndex += 1;
            var code = str.charCodeAt(i);
            var next = str.charCodeAt(i + 1);
            pairFound = (0xD800 <= code && code <= 0xDBFF && 0xDC00 <= next && next <= 0xDFFF);
            if (ucIndex == ucPos){
                codePoint = pairFound ? ((code - 0xD800) * 0x400) + (next - 0xDC00) + 0x10000 : code;
                break;
            } else{
                i += pairFound ? 2 : 1;
            }
        }
        return codePoint;
    };
}

if (!String.fromCodePoint) {
    String.fromCodePoint = function () {
        var strChars = [], codePoint, offset, codeValues, i;
        for (i = 0; i < arguments.length; ++i) {
            codePoint = arguments[i];
            offset = codePoint - 0x10000;
            if (codePoint > 0xFFFF){
                codeValues = [0xD800 + (offset >> 10), 0xDC00 + (offset & 0x3FF)];
            } else{
                codeValues = [codePoint];
            }
            strChars.push(String.fromCharCode.apply(null, codeValues));
        }
        return strChars.join("");
    };
}

if (!String.prototype.ucCharAt) {
    String.prototype.ucCharAt = function (ucIndex) {
        var str = String(this);
        var codePoint = str.codePointAt(ucIndex);
        var ucChar = String.fromCodePoint(codePoint);
        return ucChar;
    };
}

if (!String.prototype.ucIndexOf) {
    String.prototype.ucIndexOf = function (searchStr, ucStart) {
        if (isNaN(ucStart)){
            ucStart = 0;
        }
        if (ucStart < 0){
            ucStart = 0;
        }
        var str = String(this);
        var strUCLength = str.ucLength();
        searchStr = String(searchStr);
        var ucSearchLength = searchStr.ucLength();
        var i = ucStart;
        while (i < strUCLength){
            var ucSlice = str.ucSlice(i,i+ucSearchLength);
            if (ucSlice == searchStr){
                return i;
            }
            i++;
        }
        return -1;
    };
}

if (!String.prototype.ucLastIndexOf) {
    String.prototype.ucLastIndexOf = function (searchStr, ucStart) {
        var str = String(this);
        var strUCLength = str.ucLength();
        if (isNaN(ucStart)){
            ucStart = strUCLength - 1;
        }
        if (ucStart >= strUCLength){
            ucStart = strUCLength - 1;
        }
        searchStr = String(searchStr);
        var ucSearchLength = searchStr.ucLength();
        var i = ucStart;
        while (i >= 0){
            var ucSlice = str.ucSlice(i,i+ucSearchLength);
            if (ucSlice == searchStr){
                return i;
            }
            i--;
        }
        return -1;
    };
}

if (!String.prototype.ucSlice) {
    String.prototype.ucSlice = function (ucStart, ucStop) {
        var str = String(this);
        var strUCLength = str.ucLength();
        if (isNaN(ucStart)){
            ucStart = 0;
        }
        if (ucStart < 0){
            ucStart = strUCLength + ucStart;
            if (ucStart < 0){ ucStart = 0;}
        }
        if (typeof(ucStop) == 'undefined'){
            ucStop = strUCLength - 1;
        }
        if (ucStop < 0){
            ucStop = strUCLength + ucStop;
            if (ucStop < 0){ ucStop = 0;}
        }
        var ucChars = [];
        var i = ucStart;
        while (i < ucStop){
            ucChars.push(str.ucCharAt(i));
            i++;
        }
        return ucChars.join("");
    };
}

if (!String.prototype.ucSplit) {
    String.prototype.ucSplit = function (delimeter, limit) {
        var str = String(this);
        var strUCLength = str.ucLength();
        var ucChars = [];
        if (delimeter == ''){
            for (var i = 0; i < strUCLength; i++){
                ucChars.push(str.ucCharAt(i));
            }
            ucChars = ucChars.slice(0, 0 + limit);
        } else{
            ucChars = str.split(delimeter, limit);
        }
        return ucChars;
    };
}
1 голос
/ 24 декабря 2017

Более поздние движки JavaScript имеют String.<strong><a href="https://www.ecma-international.org/ecma-262/6.0/#sec-string.fromcodepoint" rel="nofollow noreferrer">fromCodePoint</a></strong>.

const ideograph = String.fromCodePoint( 0x20001 ); // outside the BMP

Также итератор кодовой точки , который возвращает вам длину кодовой точки.

function countCodePoints( str )
{
    const i = str[Symbol.iterator]();
    let count = 0;
    while( !i.next().done ) ++count;
    return count;
}

console.log( ideograph.length ); // gives '2'
console.log( countCodePoints(ideograph) ); // '1'
0 голосов
/ 25 ноября 2018

Используя инструкцию for (c of this), можно выполнять различные вычисления для строки, содержащей символы, отличные от BMP.Например, чтобы вычислить длину строки и получить n-й символ строки:

String.prototype.magicLength = function()
{
    var c, k;
    k = 0;
    for (c of this) // iterate each char of this
    {
        k++;
    }
    return k;
}

String.prototype.magicCharAt = function(n)
{
    var c, k;
    k = 0;
    for (c of this) // iterate each char of this
    {
        if (k == n) return c + "";
        k++;
    }
    return "";
}
0 голосов
/ 10 декабря 2012

Да, вы можете.Хотя поддержка не-BMP символов непосредственно в исходных документах является необязательной в соответствии со стандартом ECMAScript, современные браузеры позволяют использовать их.Естественно, кодировка документа должна быть правильно объявлена, и для большинства практических целей вам потребуется использовать кодировку UTF-8.Кроме того, вам нужен редактор, который может обрабатывать UTF-8, и вам нужны некоторые методы ввода;см., например, утилиту my Full Unicode .

Используя подходящие инструменты и настройки, вы можете написать var foo = '?'.

Символы, отличные от BMP, будут внутренне представлены как суррогатпары, поэтому каждый не-BMP символ считается как 2 в длине строки.

...