Strings JavaScript fora do BMP

BMP sendo plano multilingue básico

De acordo com o JavaScript: as boas partes :

JavaScript foi construído em um momento em que Unicode era um conjunto de caracteres de 16 bits, portanto, todos os caracteres em JavaScript têm 16 bits de largura.

Isso me leva a acreditar que o JavaScript usa UCS-2 (não UTF-16!) E só pode manipular caracteres até U + FFFF.

Outras investigações confirmam isso:

> String.fromCharCode(0x20001); 

O método fromCharCode parece usar apenas os 16 bits mais baixos ao retornar o caractere Unicode. Tentando obter U + 20001 (CJK ideograma unificado 20001) em vez disso retorna U + 0001.

Pergunta: é possível manipular caracteres pós-BMP em JavaScript?


2011-07-31: slide doze do Shootout de Suporte a Unicode: O Bom, o Mau e o (principalmente) Feio abordam muito bem questões relacionadas a isso:

    Depende do que você quer dizer com ‘suporte’. Você pode certamente colocar caracteres não-UCS-2 em uma string JS usando substitutos, e os navegadores os exibirão se puderem.

    Mas, cada item em uma string JS é uma unidade de código UTF-16 separada. Não há suporte de nível de idioma para manipular caracteres completos: todos os membros de String padrão ( length , split , slice , etc.) lidam com unidades de código e não com caracteres, portanto dividirão pares substitutos ou conterão seqüências substitutas inválidas.

    Se você quer methods que reconhecem os substitutos, receio que você tenha que começar a escrevê-los você mesmo! Por exemplo:

     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); }; 

    Cheguei à mesma conclusão que bobince. Se você quiser trabalhar com strings contendo caracteres unicode fora do BMP, você terá que reimplementar os methods String do javascript. Isso ocorre porque o javascript conta caracteres como cada valor de código de 16 bits. Símbolos fora do BMP precisam de dois valores de código para serem representados. Você, portanto, se depara com um caso em que alguns símbolos contam como dois caracteres e alguns contam apenas como um.

    Reimplementamos os seguintes methods para tratar cada ponto de código unicode como um único caractere: .length, .charCodeAt, .fromCharCode, .charAt, .indexOf, .lastIndexOf, .splice e .split.

    Você pode conferir no jsfiddle: http://jsfiddle.net/Y89Du/

    Aqui está o código sem comentários. Eu testei, mas ainda pode ter erros. Comentários são bem vindos.

     if (!String.prototype.ucLength) { String.prototype.ucLength = function() { // this solution was taken from // http://stackoverflow.com/questions/3744721/javascript-strings-outside-of-the-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; }; } 

    Mecanismos JavaScript mais recentes têm String. fromCodePoint String. fromCodePoint .

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

    Também um iterador de ponto de código , que fornece o comprimento do ponto de código.

     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' 

    Sim você pode. Embora o suporte a caracteres não-BMP diretamente nos documentos de origem seja opcional, de acordo com o padrão ECMAScript, os navegadores modernos permitem que você os use. Naturalmente, a codificação do documento deve ser adequadamente declarada e, para a maioria dos fins práticos, você precisaria usar a codificação UTF-8. Além disso, você precisa de um editor que possa manipular o UTF-8, e você precisa de alguns methods de input; veja, por exemplo, meu utilitário Full Unicode Input .

    Usando ferramentas e configurações adequadas, você pode escrever var foo = '𠀁' .

    Os caracteres não-BMP serão representados internamente como pares substitutos, portanto, cada caractere não-BMP conta como 2 no tamanho da string.