2010-09-19 17 views
33

BMP będąc Basic Multilingual Planeciągi JavaScript poza BMP

Według javascript: te dobre części:

JavaScript został zbudowany w czasie, gdy Unicode był zbiorem 16-bitowy, więc wszystko znaki w kodzie JavaScript mają szerokość 16 bitów.

To prowadzi mnie do przekonania, że ​​JavaScript używa UCS-2 (nie UTF-16!) I może obsługiwać tylko znaki do U + FFFF.

Dalsze badanie potwierdza:

> String.fromCharCode(0x20001); 

Sposób fromCharCode wydaje się używać tylko najniższe 16 bitów po powrocie znak Unicode. Próbując uzyskać U + 20001 (CJK unified ideograph 20001) zamiast tego zwraca U + 0001.

Pytanie: czy obsługa znaków post-BMP w JavaScript jest w ogóle możliwa?


2011-07-31: przesuń dwanaście z Unicode Shootout: The Good, The Bad, & The (przeważnie) brzydki obejmuje zagadnienia związane z tym całkiem dobrze:

+1

Jeśli korzystano z UTF-16, można oczekiwać, że znaki spoza podstawowej płaszczyzny wielojęzycznej będą obsługiwane przy użyciu zastępczych par. Dlaczego miałbyś oczekiwać, że zaakceptuje 32-bitową postać? –

+0

Wielkie dzięki za to, nigdy nie myślałem o tym w ten sposób. –

+2

@MichaelAaronSafyan: Ponieważ JavaScript nie ma niczego przypominającego typ "char", a 'String.fromCharCode()' zwraca ciąg, wydaje się sprawiedliwe oczekiwanie, że zwróci ciąg zawierający obie jednostki kodu, które tworzą znak. Wierzę, że będzie istnieć 'String.fromCodePoint()' dodana do przyszłego standardu JavaScript, aby to dokładnie zrobić. – hippietrail

Odpowiedz

31

Zależy od tego, co masz na myśli przez "wsparcie". Z pewnością możesz wstawiać znaki inne niż UCS-2 w ciągu JS używając surogatów, a przeglądarki będą je wyświetlać, jeśli tylko będą mogły.

Jednak każda pozycja w ciągu JS jest oddzielną jednostką kodową UTF-16. Nie ma obsługi poziomu języka dla obsługi pełnych znaków: wszystkie standardowe elementy String (length, split, slice) zajmują się jednostkami kodowymi, a nie znakami, więc całkiem chętnie podzielą pary zastępcze lub zatrzymają niepoprawne sekwencje zastępcze.

Jeśli chcesz metod zgodnych z surogatem, obawiam się, że musisz sam zacząć je pisać! Na przykład:

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

Dziękuję bardzo. To wspaniała, szczegółowa odpowiedź. –

+0

@bobince Więc, technicznie, JS używa UCS-2 lub UTF-16? UCS-2 nie obsługuje znaków spoza BMP, ale JavaScript działa, gdy poszczególne zastępcze połówki są wprowadzane indywidualnie (http://mothereff.in/js-escapes#1%F0%9D%8C%86) (np. ''\ uD834 \ uDD1E'' dla U + 1D11E). Ale czy to sprawia, że ​​UTF-16? –

+3

@Mathias: JavaScript jest utf-16-ignorantem. Daje ci sekwencję 16-bitowych jednostek kodu i pozwala ci umieścić w niej to, co lubisz. Możesz przechowywać w nim surogaty, jeśli chcesz, ale nie otrzymasz żadnych specjalnych funkcji, które mogłyby je obsłużyć jako postaci. Czy chcesz opisać to jako "używanie" UCS-2 lub UTF-16 to argument semantyczny, na który nie ma jednoznacznej odpowiedzi. Jednak niezależnie od obsługi poziomu języka w JS, inne części przeglądarki obsługują surogaty do renderowania/interakcji w interfejsie użytkownika, więc sensowne jest umieszczanie ich w łańcuchach JS. – bobince

0

Tak, możesz. Chociaż obsługa znaków spoza BMP bezpośrednio w dokumentach źródłowych jest opcjonalna zgodnie ze standardem ECMAScript, nowoczesne przeglądarki pozwalają na ich użycie. Naturalnie kodowanie dokumentu musi być właściwie zadeklarowane, a do celów praktycznych konieczne jest użycie kodowania UTF-8. Ponadto potrzebujesz edytora obsługującego kodowanie UTF-8 i potrzebujesz metody (metod) wprowadzania; patrz np. moje narzędzie Full Unicode Input.

Za pomocą odpowiednich narzędzi i ustawień można napisać var foo = ''.

Znaki inne niż BMP będą wewnętrznie reprezentowane jako pary zastępcze, więc każdy znak spoza BMP jest liczony jako 2 w długości ciągu znaków.

2

Doszedłem do tego samego wniosku, co bobince. Jeśli chcesz pracować z ciągami zawierającymi znaki Unicode poza BMP, musisz ponownie zaimplementować metody String javascript. Dzieje się tak dlatego, że javascript zlicza znaki jako każdą 16-bitową wartość kodu. Symbole spoza BMP wymagają podania dwóch wartości kodu.W związku z tym natrafiamy na przypadek, w którym niektóre symbole są liczone jako dwa znaki, a niektóre liczą się tylko jako jeden.

Ponownie zaimplementowano następujące metody traktowania każdego punktu kodu unicode jako pojedynczego znaku: .length, .charCodeAt, .fromCharCode, .charAt, .indexOf, .lastIndexOf, .splice i .split.

Można to sprawdzić na jsfiddle: http://jsfiddle.net/Y89Du/

Oto kod bez uwag. Przetestowałem to, ale nadal mogą występować błędy. Komentarze są mile widziane.

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

Dzięki! Tutaj pracujemy z emoji OS X: http://jsfiddle.net/2vWfk/ – forresto

+0

Wielkie dzięki za udostępnienie domeny publicznej. Ty, panie/pani, jesteś dżentelmenem/kobietą i uczonym. –

+0

'ucCharAt' wydaje się być zepsuty. '" ".ucCharAt (0)' zwraca poprawną wartość, ale zmienia 0 na 1 i zwraca bełkot. Zmień go na 2 i zwraca drugi (zamiast pierwszego) symbol. Tak więc, aby dostać się do ostatniego symbolu, musisz wywołać 'ucCharAt (8)', który jest większy niż długość UCLength ciągu. –

1

Nowsze silniki JavaScript mają String.fromCodePoint.

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

również code-point iterator, który dostaje długość kodu punktu.

Powiązane problemy