2010-09-19 14 views
33

BMP siendo Basic Multilingual Planecadenas JavaScript fuera del BMP

Según JavaScript: las partes buenas:

JavaScript se construyó en un momento en Unicode es un conjunto de caracteres de 16 bits, por lo que todo los caracteres en JavaScript tienen 16 bits de ancho.

Esto me lleva a creer que JavaScript usa UCS-2 (¡no UTF-16!) Y solo puede manejar caracteres hasta U + FFFF.

La investigación adicional confirma:

> String.fromCharCode(0x20001); 

El método fromCharCode parece utilizar sólo los 16 bits más bajos al devolver el carácter Unicode. Intentar obtener U + 20001 (ideograma unificado CJK 20001) en su lugar devuelve U + 0001.

Pregunta: ¿es posible manejar caracteres posteriores a BMP en JavaScript?


2011-07-31: deslice doce de Soporte Unicode tiroteo: El bueno, el malo, el & (en su mayoría) Feo cubre temas relacionados con esto muy bien:

+1

Si estuviera usando UTF-16, entonces se esperaría que los caracteres fuera del plano multilingüe básico fueran compatibles con pares de sustitución. ¿Por qué esperas que acepte un personaje de 32 bits? –

+0

Muchas gracias por eso, nunca pensé en eso de esa manera. –

+2

@MichaelAaronSafyan: como JavaScript no tiene nada parecido a un tipo "char" y 'String.fromCharCode()' devuelve una cadena, parece justo esperar que devuelva una cadena que contenga las dos unidades de código que componen el carácter. Creo que habrá un 'String.fromCodePoint()' agregado a un futuro estándar de JavaScript para hacer exactamente eso. – hippietrail

Respuesta

31

Depende de lo que quiere decir con 'soporte'. Ciertamente puedes poner caracteres que no sean UCS-2 en una cadena JS usando sustitutos, y los navegadores los mostrarán si pueden.

Pero, cada elemento en una cadena JS es una unidad de código UTF-16 por separado. No hay soporte de nivel de idioma para manejar caracteres completos: todos los miembros de String estándar (length, split, slice, etc.) tratan todas las unidades de código, no los caracteres, por lo que muy felizmente dividirán pares de sustitución o mantendrán secuencias sustitutas no válidas.

Si quiere utilizar métodos de subrogación, ¡me temo que tendrá que empezar a escribirlos usted mismo! Por ejemplo:

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

Muchas gracias. Esa es una gran respuesta detallada. –

+0

@bobince Entonces, técnicamente, ¿utiliza JS UCS-2 o UTF-16? UCS-2 no admite caracteres fuera del BMP, pero JavaScript sí si se ingresan las mitades sustitutas individuales [individualmente] (http://mothereff.in/js-escapes#1%F0%9D%8C%86) (por ej. ''\ uD834 \ uDD1E'' para U + 1D11E). Pero, ¿eso lo convierte en UTF-16? –

+3

@Mathias: JavaScript es UTF-16-ignorante. Le da una secuencia de unidades de código de 16 bits y le permite poner lo que quiera en ella. Puede almacenar sustitutos si lo desea, pero no obtendrá ninguna característica especial para manejarlos como personajes. Si desea describir eso como 'usar' UCS-2 o UTF-16 es un argumento semántico para el cual no hay una respuesta definitiva. Sin embargo, independientemente del soporte de nivel de idioma en JS, otras partes del navegador admiten substitutos para la representación/interacción en la interfaz de usuario, por lo que tiene sentido incluirlos en cadenas JS. – bobince

0

Sí, puede hacerlo. Aunque la compatibilidad con los caracteres que no son BMP directamente en los documentos fuente es opcional de acuerdo con el estándar ECMAScript, los navegadores modernos te permiten usarlos. Naturalmente, la codificación del documento debe declararse correctamente y, para la mayoría de los propósitos prácticos, deberá utilizar la codificación UTF-8. Además, necesita un editor que pueda manejar UTF-8, y necesita algún método de entrada; ver p. mi utilidad Full Unicode Input.

Usando herramientas y configuraciones adecuadas, puede escribir var foo = ''.

Los caracteres que no son BMP se representarán internamente como pares de sustitución, por lo que cada carácter no BMP cuenta como 2 en la longitud de la cadena.

2

Llegué a la misma conclusión que bobince. Si desea trabajar con cadenas que contienen caracteres Unicode fuera del BMP, debe volver a implementar los métodos String de javascript. Esto es porque javascript cuenta caracteres como cada valor de código de 16 bits. Los símbolos fuera del BMP necesitan dos valores de código para ser representados.Por lo tanto, se encontrará con un caso en el que algunos símbolos cuentan como dos caracteres y algunos cuentan solo como uno.

He vuelto a implementar los siguientes métodos para tratar cada punto de código Unicode como un solo carácter: .length, .charCodeAt, .fromCharCode, .charAt, .indexOf, .lastIndexOf, .splice y .split.

Puede encontrar esta información en jsFiddle: http://jsfiddle.net/Y89Du/

Aquí está el código sin comentarios. Lo probé, pero aún puede tener errores. Los comentarios son bienvenidos

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; 
    }; 
} 
+0

Gracias! Aquí está trabajando con emojis de OS X: http://jsfiddle.net/2vWfk/ – forresto

+0

Muchas gracias por publicarlo en el dominio público. Usted, señor/señora, es un caballero/mujer y un erudito. –

+0

'ucCharAt' parece estar roto. '" ".ucCharAt (0)' devuelve el valor correcto pero cambia el 0 a un 1 y devuelve un galimatías. Cambie a 2 y devuelve el segundo símbolo (en lugar del primero). Entonces, para llegar al último símbolo, debe llamar a 'ucCharAt (8)' que es más grande que ucLength de la cadena. –

1

Los motores de JavaScript más recientes tienen String.fromCodePoint.

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

También un code-point iterator, que le consigue la longitud del código de punto.

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' 
Cuestiones relacionadas