2010-09-19 5 views
33

BMP является Basic Multilingual Planeстроки JavaScript за пределами BMP

Согласно JavaScript: хорошие части:

JavaScript был построен в то время, когда Unicode был 16-битный набор символов, так что все символы в формате JavaScript имеют ширину 16 бит.

Это заставляет меня думать, что JavaScript использует UCS-2 (не UTF-16!) И может обрабатывать символы только до U + FFFF.

Дальнейшее исследование подтверждает это:

> String.fromCharCode(0x20001); 

Метод fromCharCode, кажется, используют только самые низкие 16 бит при возврате символа Unicode. Попытка получить U + 20001 (унифицированный идеолог CJK 20001) вместо этого возвращает U + 0001.

Вопрос: возможно ли обработать символы после BMP в JavaScript?


2011-07-31: слайд двенадцать из Поддержка Unicode выбывание: The Good, The Bad, & (в основном) Гадкий охватывает вопросы, связанные с этим довольно хорошо:

+1

Если он использует UTF-16, то можно ожидать, что символы, находящиеся за пределами базовой многоязычной плоскости, будут поддерживаться с использованием суррогатных пар. Почему вы ожидаете, что он примет 32-битный символ? –

+0

Большое спасибо за это, я так и не подумал об этом. –

+2

@MichaelAaronSafyan: Поскольку JavaScript не имеет ничего похожего на тип «char», а 'String.fromCharCode()' возвращает строку, кажется справедливым ожидать, что она вернет строку, содержащую оба блока кода, которые составляют символ. Я верю, что будет 'String.fromCodePoint()' добавлен в будущий стандарт JavaScript, чтобы сделать именно это. – hippietrail

ответ

31

Зависит от того, что вы подразумеваете под «поддержкой». Вы можете, конечно, поместить символы не-UCS-2 в строку JS, используя суррогаты, и браузеры будут отображать их, если они могут.

Но каждый элемент в строке JS является отдельным блоком кода UTF-16. Существует не поддержка языкового уровня для обработки полных символов: все стандартные члены String (length, split, slice и т. Д.) Все имеют дело с кодовыми единицами, а не символами, поэтому вполне счастливо разделяют суррогатные пары или удерживают недействительные суррогатные последовательности.

Если вы хотите методы, основанные на суррогатах, я боюсь, что вам придется начинать писать их сами! Например:

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

Большое спасибо. Это отличный, подробный ответ. –

+0

@bobince Итак, технически, использует ли JS UCS-2 или UTF-16? UCS-2 не поддерживает символы вне BMP, но JavaScript делает, если отдельные суррогатные половинки введены [индивидуально] (http://mothereff.in/js-escapes#1%F0%9D%8C%86) (например, '' \ uD834 \ uDD1E'' для U + 1D11E). Но делает ли это UTF-16? –

+3

@ Mathias: JavaScript является UTF-16-неосведомленным. Он дает вам последовательность из 16-битных блоков кода и позволяет вам размещать в нем то, что вам нравится. Вы можете хранить в ней суррогаты, если хотите, но вы не получите никаких специальных функций, чтобы обрабатывать их как символы. Если вы хотите описать это как «использование» UCS-2 или UTF-16, это семантический аргумент, для которого нет однозначного ответа. Однако, независимо от поддержки на уровне языка в JS, другие части браузера поддерживают суррогаты для визуализации/взаимодействия в пользовательском интерфейсе, поэтому имеет смысл включать их в строки JS. – bobince

0

Да, вы можете. Хотя поддержка символов, отличных от BMP, непосредственно в исходных документах является необязательной в соответствии со стандартом ECMAScript, современные браузеры позволяют использовать их. Естественно, что кодировка документа должна быть правильно объявлена, и для большинства практических целей вам потребуется использовать кодировку UTF-8. Кроме того, вам нужен редактор, который может обрабатывать UTF-8, и вам нужны некоторые методы ввода; см., например, мой Full Unicode Input утилита.

Используя соответствующие инструменты и настройки, вы можете написать var foo = ''.

Символы, отличные от BMP, будут внутренне представлены в виде суррогатных пар, поэтому каждый символ без BMP считается равным 2 в строке.

2

Я пришел к тому же выводу, что и bobince. Если вы хотите работать со строками, содержащими символы Unicode вне BMP, вам необходимо переопределить методы String javascript. Это связано с тем, что javascript подсчитывает символы как каждое 16-битное значение кода. Символы вне BMP должны быть представлены двумя значениями кода.Таким образом, вы сталкиваетесь с ситуацией, когда некоторые символы считаются двумя символами, а некоторые считаются единственными.

Я пересмотрел следующие методы для обработки каждой кодовой точки юникода как одного символа: .length, .charCodeAt, .fromCharCode, .charAt, .indexOf, .lastIndexOf, .splice и .split.

Вы можете проверить это на jsfiddle: http://jsfiddle.net/Y89Du/

Вот код без комментариев. Я тестировал его, но он все еще может иметь ошибки. Комментарии приветствуются.

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

Спасибо! Здесь работает с OS X emoji: http://jsfiddle.net/2vWfk/ – forresto

+0

Большое спасибо за публикацию в общественном достоянии. Вы, сэр/мадам, - джентльмен/женщина и ученый. –

+0

'ucCharAt' похоже сломан. '" ".ucCharAt (0)' возвращает правильное значение, но меняет 0 на 1 и возвращает тарабарщину. Измените его на 2, и он вернет второй (вместо первого) символ. Итак, чтобы перейти к последнему символу, вы должны вызвать 'ucCharAt (8)', который больше, чем длина ucLength строки. –

1

Новые двигатели JavaScript имеют String.fromCodePoint.

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

Также в code-point iterator, который получает вас длину кода точки.

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' 
Смежные вопросы