Разделить строку JavaScript на массив кодовых точек? (с учетом суррогатных пар, но не графемных кластеров)

Разделение строки JavaScript на «символы» может быть выполнено тривиально, но есть проблемы, если вы заботитесь о Unicode (и вам следует заботиться о Unicode).

JavaScript изначально обрабатывает символы как 16-битные объекты (UCS-2 или UTF-16), но это не позволяет использовать символы Юникода за пределами BMP (базовая многоязычная плоскость) .

Чтобы работать с символами Unicode помимо BMP, JavaScript должен учитывать "суррогатные пары", чего он не делает. изначально.

Я ищу, как разбить строку js по кодовой точке, независимо от того, требуется ли для кодовых точек один или два «символа» JavaScript (единицы кода).

В зависимости от ваших потребностей, разделения по кодовой точке может быть недостаточно, и вы можете захотеть разделить на " кластер графемы ", где кластер является базовой кодовой точкой, за которой следуют все его кодовые точки модификатора без пробелов, такие как сочетание ударений и диакритических знаков.

Для этого вопроса я не требую разбиения по кластерам графем.


person hippietrail    schedule 28.01.2014    source источник


Ответы (4)


Ответ @bobince (к счастью) немного устарел; теперь вы можете просто использовать

var chars = Array.from( text )

для получения списка строк с одним кодом, который учитывает астральные / 32-битные / суррогатные символы Unicode.

person John Frazer    schedule 04.03.2017

В соответствии со строками ответа @John Frazer можно использовать эту даже краткую форму строковой итерации:

const chars = [...text]

например, с:

const text = 'A\uD835\uDC68B\uD835\uDC69C\uD835\uDC6A'
const chars = [...text] // ["A", "????", "B", "????", "C", "????"]
person Brett Zamir    schedule 26.09.2018
comment
лучший ответ, если любите лаконичность. - person johny why; 07.05.2020
comment
это также работает, когда фактические графические символы вставляются в строку (если ваша IDE поддерживает это) - person johny why; 07.05.2020

В ECMAScript 6 вы сможете использовать строку в качестве итератора для получения кодовых точек, или вы можете искать строку для /./ug, или вы можете повторно вызывать getCodePointAt(i).

К сожалению, синтаксис _3 _.._ 4_ и флаги регулярного выражения не могут быть полифилированы, а вызов полифилла getCodePoint() будет очень медленным (O (n²)), поэтому мы пока не можем реально использовать этот подход.

Итак, сделаем это вручную:

String.prototype.toCodePoints= function() {
    chars = [];
    for (var i= 0; i<this.length; i++) {
        var c1= this.charCodeAt(i);
        if (c1>=0xD800 && c1<0xDC00 && i+1<this.length) {
            var c2= this.charCodeAt(i+1);
            if (c2>=0xDC00 && c2<0xE000) {
                chars.push(0x10000 + ((c1-0xD800)<<10) + (c2-0xDC00));
                i++;
                continue;
            }
        }
        chars.push(c1);
    }
    return chars;
}

Обратное к этому см. На странице https://stackoverflow.com/a/3759300/18936.

person bobince    schedule 28.01.2014
comment
getCodePointAt - это O(n). Аргумент, который он принимает, является не индексом кодовой точки, а индексом кодовой единицы (обычным строковым индексом). - person glebm; 27.08.2017
comment
@glebm ты имел ввиду, что getCodePointAt это O(1)? - person Roland Illig; 28.12.2020
comment
Да, O(1), я больше не могу редактировать комментарий - person glebm; 29.12.2020

Другой метод с использованием codePointAt:

String.prototype.toCodePoints = function () {
  var arCP = [];
  for (var i = 0; i < this.length; i += 1) {
    var cP = this.codePointAt(i);
    arCP.push(cP);
    if (cP >= 0x10000) {
      i += 1;
    }
  }
  return arCP;
}
person saltimbokka    schedule 11.04.2020