2010-09-19 15 views
33

BMP essendo Basic Multilingual Planestringhe JavaScript al di fuori del BMP

Secondo JavaScript: le parti buone:

è stato costruito JavaScript in un momento in Unicode è un set di caratteri a 16 bit, in modo che tutti i caratteri in JavaScript sono larghi 16 bit.

Questo mi porta a credere che JavaScript usi UCS-2 (non UTF-16!) E possa gestire solo caratteri fino a U + FFFF.

Ulteriori indagini conferma:

> String.fromCharCode(0x20001); 

Il metodo fromCharCode sembra di utilizzare solo il più basso 16 bit quando si ritorna il carattere Unicode. Cercando di ottenere U + 20001 (ideogramma unificato CJK 20001) restituisce invece U + 0001.

Domanda: è possibile gestire i caratteri post-BMP in JavaScript?


2011-07-31: far scorrere dodici da Supporto Unicode Shootout: Il buono, il brutto, il & (per lo più) Ugly copre le questioni relative a questo piuttosto bene:

+1

Se si stesse utilizzando UTF-16, ci si aspetterebbe che i caratteri al di fuori del piano multilingue di base siano supportati utilizzando coppie di surrogati. Perché ti aspetteresti che accetti un personaggio a 32 bit? –

+0

Grazie mille per questo, non ci ho mai pensato in questo modo. –

+2

@MichaelAaronSafyan: Poiché JavaScript non ha nulla che assomigli a un tipo "char" e 'String.fromCharCode()' restituisce una stringa, sembra giusto aspettarsi che restituisca una stringa contenente entrambe le unità di codice che costituiscono il carattere. Credo che ci sarà un 'String.fromCodePoint()' aggiunto ad un futuro standard JavaScript per fare esattamente questo. – hippietrail

risposta

31

Dipende da cosa intendi per "supporto". Puoi certamente inserire caratteri non-UCS-2 in una stringa JS usando surrogati, e i browser li visualizzeranno se possono.

Tuttavia, ogni elemento in una stringa JS è un'unità di codice UTF-16 separata. Non v'è alcun supporto a livello di lingua per la gestione di caratteri interi: tutti i membri standard String (length, split, slice ecc) tutti affrontare non unità di codice personaggi, per cui le coppie di surrogati sarà tranquillamente dividere o tenere sequenze surrogati validi.

Se si desidera utilizzare i metodi relativi alla funzione surrogata, temo che si debba iniziare a scriverli da soli! Ad esempio:

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

Grazie mille. Questa è una risposta grandiosa e dettagliata. –

+0

@bobince Quindi, tecnicamente, JS usa UCS-2 o UTF-16? UCS-2 non supporta caratteri al di fuori del BMP, ma JavaScript lo fa se le singole metà di surrogato vengono immesse [singolarmente] (http://mothereff.in/js-escapes#1%F0%9D%8C%86) (ad es. ''\ uD834 \ uDD1E'' per U + 1D11E). Ma questo lo rende UTF-16? –

+3

@ Mathias: JavaScript è UTF-16-ignorante. Ti dà una sequenza di unità di codice a 16 bit e ti consente di inserire ciò che ti piace. Puoi archiviare i surrogati se lo desideri, ma non avrai caratteristiche speciali per gestirli come personaggi. Che tu voglia descriverlo come "usare" UCS-2 o UTF-16 è un argomento semantico al quale non esiste una risposta definitiva. Tuttavia, indipendentemente dal supporto a livello di lingua in JS, altre parti del browser supportano i surrogati per il rendering/l'interazione nell'interfaccia utente, quindi ha senso includerli nelle stringhe JS. – bobince

0

Sì, è possibile. Sebbene il supporto per i caratteri non BMP direttamente nei documenti di origine sia facoltativo in base allo standard ECMAScript, i browser moderni consentono di utilizzarli. Naturalmente, la codifica del documento deve essere dichiarata correttamente, e per la maggior parte degli scopi pratici è necessario utilizzare la codifica UTF-8. Inoltre, è necessario un editor in grado di gestire UTF-8 e sono necessari alcuni metodi di input; vedere per es. il mio programma di utilità Full Unicode Input.

Utilizzando gli strumenti e le impostazioni appropriati, è possibile scrivere var foo = ''.

La non-BMP caratteri vengono rappresentati internamente come coppie di surrogati, così ogni non-BMP caratteri vale 2 nella lunghezza della stringa.

2

Sono giunto alla stessa conclusione di Bobince. Se si desidera lavorare con stringhe contenenti caratteri Unicode al di fuori del BMP, è necessario reimplementare i metodi String di javascript. Questo perché javascript conta i caratteri come ogni valore di codice a 16 bit. I simboli al di fuori del BMP richiedono la rappresentazione di due valori di codice.Quindi si imbatte in un caso in cui alcuni simboli contano come due caratteri e alcuni contano solo come uno.

Ho reimplementato i seguenti metodi per trattare ciascun punto di codice Unicode come un singolo carattere: .length, .charCodeAt, .fromCharCode, .charAt, .indexOf, .lastIndexOf, .splice e .split.

È possibile controllare sul jsfiddle: http://jsfiddle.net/Y89Du/

Ecco il codice senza commenti. L'ho provato, ma potrebbe ancora avere errori. I commenti sono i benvenuti

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

Grazie! Qui sta lavorando con OS X emoji: http://jsfiddle.net/2vWfk/ – forresto

+0

Mille grazie per il rilascio in pubblico dominio. Lei, signore/signora, sono un gentiluomo/donna e uno studioso. –

+0

'ucCharAt' sembra essere rotto. '" ".ucCharAt (0)' restituisce il valore corretto ma cambia lo 0 in a 1 e restituisce senza senso. Cambiarlo in 2 e restituisce il secondo simbolo (anziché il primo). Quindi per arrivare all'ultimo simbolo, devi chiamare 'ucCharAt (8)' che è più grande della lunghezza della stringa ucLength. –

1

I motori JavaScript più recenti hanno String.fromCodePoint.

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

Anche un code-point iterator, che si ottiene la lunghezza del codice-point.

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'