Ответ 2
Кодировка Unicode и UTF-8, UTF-16, UTF-32
Unicode - это набор символов, который определяет сопоставление от символов к кодовым точкам, а кодировки символов (UTF-8, UTF-16, UTF-32) определяют, как хранить кодовые точки Юникода.
В Unicode символ отображается на одну кодовую точку, но может иметь различное представление в зависимости от того, как оно закодировано.
Я не хочу повторять эту дискуссию снова, поэтому, если вы до сих пор не совсем поняли об этом, прочитайте Абсолютный минимум Каждый разработчик программного обеспечения Абсолютно, Положительно должен знать об Unicode и наборах символов (без отговорок!).
Используя пример в вопросе, 𡃁
сопоставляется с кодовой точкой U+210C1
, но он может быть закодирован как F0 A1 83 81
в UTF-8, D844 DCC1
в UTF-16 и 000210C1
в UTF- 32.
Чтобы быть точным, приведенный выше пример показывает, как сопоставить точку кода с кодовыми единицами (форму кодировки символов). То, как модули кода сопоставляются с октетной последовательностью, - другое дело. См. Модель кодировки Unicode
PCRE 8-разрядная, 16-битная и 32-разрядная библиотека
Так как PHP еще не принял PCRE2 (версия 10.10), цитируемый текст из документации исходного PCRE.
Поддержка 16-битной и 32-разрядной библиотеки
PCRE включает поддержку 16-разрядной строки в версии 8.30 и 32-битную строку из версии 8.32, в дополнение к 8-разрядной библиотеке по умолчанию.
Помимо поддержки 8-битных символьных строк, PCRE также поддерживает 16-битные строки (начиная с версии 8.30) и 32-разрядные строки (начиная с версии 8.32), с помощью двух дополнительных библиотек. Они могут быть построены, а также, а не 8-битная библиотека. [...]
Значение 8-битного, 16-битного, 32-разрядного
8-битная, 16-битная и 32-разрядная здесь относится к блоку данных (блок кода).
Ссылки на байты и UTF-8 в этом документе следует читать как ссылки на 16-битные единицы данных и UTF-16 при использовании 16-битной библиотеки или 32-битных блоков данных и UTF-32 при использовании 32 -битной библиотеки, если не указано иное. Более подробная информация о конкретных отличиях для 16-разрядных и 32-разрядных библиотек приведена на страницах pcre16 и pcre32.
Это означает, что 8-битная/16-разрядная/32-битная библиотека ожидает, что шаблон и входная строка будут последовательностями 8-битных/16-разрядных/32-битных блоков данных или действительных UTF-8/UTF -16/UTF-32.
Различные API-интерфейсы для различной ширины блока данных
PCRE предоставляет 3 набора идентичных API для 8-разрядных, 16-битных и 32-битных библиотек, дифференцированных префиксными (pcre_
, pcre16_
и pcre_32
соответственно).
16-битные и 32-битные функции работают так же, как и их 8-битные аналоги; они просто используют разные типы данных для своих аргументов и результатов, а их имена начинаются с pcre16_
или pcre32_
вместо pcre_
. Для каждой опции, которая имеет UTF8 в своем имени (например, PCRE_UTF8
), имеются соответствующие 16-битные и 32-битные имена с заменой UTF8 на UTF16 или UTF32 соответственно. Этот объект на самом деле просто косметический; 16-битные и 32-битные имена опций определяют одинаковые значения бит.
В PCRE2 используется соглашение об именовании аналогичных функций, где 8-битная/16-разрядная/32-битная функция имеет _8
, _16
, _32
соответственно. Приложения, которые используют только одну ширину блока кода, могут определять PCRE2_CODE_UNIT_WIDTH
для использования общего имени функции без суффикса.
Режим UTF против режима без UTF
Если установлен режим UTF (через опцию (*UTF)
, (*UTF8)
, (*UTF16)
, (*UTF32)
1 или параметры компиляции PCRE_UTF8
, PCRE_UTF16
, PCRE_UTF32
), все последовательности блоков данных интерпретируются как последовательности символов Unicode, которые состоят из всех кодовых точек от U + 0000 до U + 10FFFF, за исключением суррогатов и спецификации.
1 Параметры в шаблоне (*UTF8)
, (*UTF16)
, (*UTF32)
доступны только в соответствующей библиотеке. Вы не можете использовать (*UTF16)
в 8-битной библиотеке или любую несогласованную комбинацию, поскольку она просто не имеет смысла. (*UTF)
доступен во всех библиотеках и предоставляет переносимый способ указания режима UTF в шаблоне.
В режиме UTF шаблон (который является последовательностью блоков данных) интерпретируется и проверяется как последовательность кодовых точек Unicode, декодируя последовательность как данные UTF-8/UTF-16/UTF-32 (в зависимости от API), прежде чем он будет скомпилирован. Строка ввода также интерпретируется и опционально проверяется как последовательность кодовых точек Unicode во время процесса сопоставления. В этом режиме класс символов соответствует одной допустимой кодовой точке Юникода.
С другой стороны, когда режим UTF не установлен (не-UTF-режим), все операции непосредственно работают с последовательностями блоков данных. В этом режиме класс символов соответствует одному блоку данных и за исключением максимального значения, которое может быть сохранено в одном блоке данных, нет ограничений на значение единицы данных. Этот режим может использоваться для сопоставления структуры в двоичных данных. Тем не менее, не используют этот режим, когда вы имеете дело с символом Unicode, ну, если вы не в порядке с ASCII и игнорируете остальные языки.
Ограничения на символьные значения
Символы, которые указаны с использованием восьмеричных или шестнадцатеричных чисел, ограничены определенными значениями следующим образом:
8-bit non-UTF mode less than 0x100
8-bit UTF-8 mode less than 0x10ffff and a valid codepoint
16-bit non-UTF mode less than 0x10000
16-bit UTF-16 mode less than 0x10ffff and a valid codepoint
32-bit non-UTF mode less than 0x100000000
32-bit UTF-32 mode less than 0x10ffff and a valid codepoint
Недопустимыми кодовыми точками Unicode являются диапазон от 0xd800 до 0xdfff (так называемые "суррогатные" кодовые точки) и 0xffef.
PHP и PCRE
функции PCRE в PHP реализованы оболочкой, которая переводит PHP -специфические флаги и вызовы в API PCRE (как показано в ветке PHP 5.6.10).
Исходный код вызывается в 8-битный библиотечный API PCRE (pcre_
), поэтому любая строка, переданная в функцию preg_
, интерпретируется как последовательность 8-разрядных блоков данных (байты). Поэтому, даже если построены 16-разрядные и 32-разрядные библиотеки PCRE, они не доступны через API на стороне PHP вообще.
В результате функции PCRE в PHP ожидают:
- ... массив байтов в режиме без UTF (по умолчанию), который библиотека читает в 8-битных "символах" и компилирует для соответствия строкам 8-разрядных "символов".
- ... массив байтов, который содержит кодировку Unicode UTF-8, которую библиотека читает в символах Unicode и компилирует, чтобы соответствовать строкам Unicode UTF-8.
Это объясняет поведение, как показано в вопросе:
- В режиме, отличном от UTF (без флага
u
), максимальное значение в шестнадцатеричной управляющей последовательности регулярного выражения представляет собой FF (как показано в [\x{00}-\x{ff}]
)
- В режиме UTF любое значение, выходящее за пределы 0x10ffff (например,
\x{7fffffff}
) в шестнадцатеричной escape-последовательности регулярного выражения, просто не имеет смысла.
Пример кода
Этот пример кода демонстрирует:
- Строки PHP - это просто массивы байтов и ничего не понимают в кодировке.
- Различия между режимами UTF и не-UTF в функции PCRE.
- Функции вызова PCRE в 8-битную библиотеку
// NOTE: Save this file as UTF-8
// Take note of double-quoted string literal, which supports escape sequence and variable expansion
// The code won't work correctly with single-quoted string literal, which has restrictive escape syntax
// Read more at: https://php.net/language.types.string
$str_1 = "\xf0\xa1\x83\x81\xf0\xa1\x83\x81";
$str_2 = "𡃁𡃁";
$str_3 = "\xf0\xa1\x83\x81\x81\x81\x81\x81\x81";
echo ($str_1 === $str_2)."\n";
var_dump($str_3);
// Test 1a
$match = null;
preg_match("/\xf0\xa1\x83\x81+/", $str_1, $match);
print_r($match); // Only match 𡃁
// Test 1b
$match = null;
preg_match("/\xf0\xa1\x83\x81+/", $str_2, $match);
print_r($match); // Only match 𡃁 (same as 1a)
// Test 1c
$match = null;
preg_match("/\xf0\xa1\x83\x81+/", $str_3, $match);
print_r($match); // Match 𡃁 and the five bytes of 0x81
// Test 2a
$match = null;
preg_match("/𡃁+/", $str_1, $match);
print_r($match); // Only match 𡃁 (same as 1a)
// Test 2b
$match = null;
preg_match("/𡃁+/", $str_2, $match);
print_r($match); // Only match 𡃁 (same as 1b and 2a)
// Test 2c
$match = null;
preg_match("/𡃁+/", $str_3, $match);
print_r($match); // Match 𡃁 and the five bytes of 0x81 (same as 1c)
// Test 3a
$match = null;
preg_match("/\xf0\xa1\x83\x81+/u", $str_1, $match);
print_r($match); // Match two 𡃁
// Test 3b
$match = null;
preg_match("/\xf0\xa1\x83\x81+/u", $str_2, $match);
print_r($match); // Match two 𡃁 (same as 3a)
// Test 4a
$match = null;
preg_match("/𡃁+/u", $str_1, $match);
print_r($match); // Match two 𡃁 (same as 3a)
// Test 4b
$match = null;
preg_match("/𡃁+/u", $str_2, $match);
print_r($match); // Match two 𡃁 (same as 3b and 4a)
Так как строки PHP - это просто массив байтов, если файл сохранен правильно в некотором ASCII-совместимом кодировании, PHP просто с радостью прочитает байты, не заботясь о том, какая кодировка была изначально. Программист несет полную ответственность для правильной кодировки и декодирования строк.
Из-за вышеуказанной причины, если вы сохраните файл выше в кодировке UTF-8, вы увидите, что $str_1
и $str_2
- это одна и та же строка. $str_1
является декодированием из escape-последовательности, а $str_2
читается дословно из исходного кода. В результате "/\xf0\xa1\x83\x81+/u"
и "/𡃁+/u"
являются одной и той же строкой внизу (также для "/\xf0\xa1\x83\x81+/"
и "/𡃁+/"
).
Различие между режимом UTF и режимом без UTF четко показано в приведенном выше примере:
-
"/𡃁+/"
рассматривается как последовательность символов F0 A1 83 81 2B
, где "символ" - один байт. Следовательно, полученное регулярное выражение соответствует последовательности F0 A1 83
, за которой следует байт 81
, повторяющийся один или несколько раз.
-
"/𡃁+/u"
проверяется и интерпретируется как последовательность символов UTF-8 U+210C1 U+002B
. Поэтому полученное регулярное выражение соответствует кодовой точке U+210C1
, повторяемой один или несколько раз в строке UTF-8.
Соответствие символу Unicode
Если вход не содержит других двоичных данных, настоятельно рекомендуется всегда включать режим u
. Шаблон имеет доступ ко всем средствам для правильного соответствия символам Юникода, и как вход, так и шаблон проверяются как допустимые строки UTF.
Опять же, используя пример 𡃁
в качестве примера, приведенный выше пример показывает два способа задания регулярного выражения:
"/\xf0\xa1\x83\x81+/u"
"/𡃁+/u"
Первый метод не работает с одиночной кавычкой - поскольку \x
escape-последовательность не распознается в одинарной катете, библиотека получит строку \xf0\xa1\x83\x81+
, которая в сочетании с режимом UTF будет соответствовать U+00F0 U+00A1 U+0083
, а затем U+0081
повторяется один или несколько раз. Помимо этого, это также путает следующего человека, читающего код: как они должны знать, что один символ Unicode повторяется один или несколько?
Второй метод работает хорошо, и его можно даже использовать с одной кавычкой, но вам нужно сохранить файл в кодировке UTF-8, особенно в случае с символами типа ÿ
, так как символ также действителен в однобайтовая кодировка. Этот метод является опцией, если вы хотите совместить один символ или последовательность символов. Однако, как конечные точки диапазона символов, может быть неясно, к чему вы пытаетесь соответствовать. Сравните a-z
, a-z
, 0-9
, א-ת
, в отличие от 一-龥
(который соответствует большей части Блок Unified Ideographs CJK (4E00- 9FFF), за исключением неназначенных кодовых точек в конце) или 一-十
(что является неправильной попыткой сопоставить китайские символы с номером от 1 до 10).
Третий метод заключается в том, чтобы напрямую указать код в шестнадцатеричном escape-адресе:
"/\x{210C1}/u"
'/\x{210C1}/u'
Это работает, когда файл сохраняется в любой кодировке, совместимой с ASCII, работает как с одиночной, так и с двойной кавычкой, а также дает четкую кодовую точку в диапазоне символов. Этот метод имеет недостаток, заключающийся в том, что вы не знаете, как выглядит персонаж, и его также трудно прочитать при указании последовательности символов Юникода.
Ответ 3
Я не уверен в php, но на кодовых точках действительно нет губернатора
поэтому не имеет значения, что есть только 1,1 миллиона действительных.
Это может быть изменено в любое время, но это не реально для двигателей.
для обеспечения этого. Есть зарезервированные cp, которые являются отверстиями в допустимом диапазоне,
есть суррогаты в действующем диапазоне, причины бесконечны для там
чтобы не было другого ограничения, кроме размера слова.
Для UTF-32 вы не можете переходить через 31 бит, потому что 32 - бит знака.
0x00000000 - 0x7FFFFFFF
Имеет смысл с unsigned int
, поскольку тип данных является естественным размером 32-разрядных аппаратных регистров.
Для UTF-16, даже более верное, вы можете увидеть одно и то же ограничение, замаскированное до 16 бит.
Бит 32 по-прежнему является битом знака, оставляя 0x0000 - 0xFFFF
допустимым диапазоном.
Обычно, если вы используете движок, поддерживающий ICU, вы сможете его использовать,
который преобразует как исходный, так и регулярный выражения в UTF-32. Boost Regex - один из таких движков.
изменить:
Что касается UTF-16
Я думаю, когда Unicode перешел на 16 бит, они пробивали отверстие в 16-битном диапазоне для суррогатных пар. Но он оставил только 20 полных битов между парой как пригодный для использования.
10 бит в каждом суррогате с другим 6, используемым для определения hi или lo.
Похоже, что это оставило людей Юникода с пределом 20 бит + дополнительный 0xFFFF округленный, в общей сложности 0x10FFFF кодов, с неиспользуемыми дырками.
Чтобы иметь возможность конвертировать в другую кодировку (8/16/32) все кодовые страницы
должен быть фактически конвертируемым. Таким образом, навсегда отсталая совместимая 20-бит - это
ловушку, с которой они столкнулись раньше, но теперь должны жить.
Независимо от того, что двигатели регулярных выражений не будут применять этот предел в ближайшее время, возможно, никогда.
Что касается суррогатов, то они являются дырой, и неверно сформированный буквальный суррогат не может быть преобразован между режимами. Это просто относится к буквальному кодированному символу во время преобразования, а не к шестнадцатеричному представлению одного. Например, его легко найти текст в режиме UTF-16 (только) для непарных суррогатов или даже парных.
Но я думаю, что двигатели регулярных выражений действительно не заботятся о дырах или ограничениях, им все равно, в каком режиме находится строка темы. Нет, движок не собирается говорить:
"Эй, подождите, режим UTF-16 лучше конвертировать \x{210C1}
в \x{D844}\x{DCC1}
. Подождите, если я это сделаю, что мне делать, если его квантифицированный \x{210C1}+
, начнет создавать внутри него регулярные выражения? Хуже того, что, если его в классе [\x{210C1}]
? Nah.. лучше ограничьте его до \x{FFFF}
.
Некоторые удобные конверсии суррогатных псевдонимов, которые я использую:
Definitions:
====================
10-bits
3FF = 000000 1111111111
Hi Surrogate
D800 = 110110 0000000000
DBFF = 110110 1111111111
Lo Surrogate
DC00 = 110111 0000000000
DFFF = 110111 1111111111
Conversions:
====================
UTF-16 Surrogates to UTF-32
if ( TESTFOR_SURROGATE_PAIR(hi,lo) )
{
u32Out = 0x10000 + ( ((hi & 0x3FF) << 10) | (lo & 0x3FF) );
}
UTF-32 to UTF-16 Surrogates
if ( u32In >= 0x10000)
{
u32In -= 0x10000;
hi = (0xD800 + ((u32In & 0xFFC00) >> 10));
lo = (0xDC00 + (u32In & 0x3FF));
}
Macro's:
====================
#define TESTFOR_SURROGATE_HI(hs) (((hs & 0xFC00)) == 0xD800 )
#define TESTFOR_SURROGATE_LO(ls) (((ls & 0xFC00)) == 0xDC00 )
#define TESTFOR_SURROGATE_PAIR(hs,ls) ( (((hs & 0xFC00)) == 0xD800) && (((ls & 0xFC00)) == 0xDC00) )
//
#define PTR_TESTFOR_SURROGATE_HI(ptr) (((*ptr & 0xFC00)) == 0xD800 )
#define PTR_TESTFOR_SURROGATE_LO(ptr) (((*ptr & 0xFC00)) == 0xDC00 )
#define PTR_TESTFOR_SURROGATE_PAIR(ptr) ( (((*ptr & 0xFC00)) == 0xD800) && (((*(ptr+1) & 0xFC00)) == 0xDC00) )