Без использования u
используется шестиугольный диапазон, который можно использовать [\x{00}-\x{ff}]
, но с флагом u
он достигает 4-байтового значения \x{7fffffff}
( [\x{00000000}-\x{7fffffff}]
).
Поэтому, если я выполнил приведенный ниже код:
preg_match("/[\x{00000000}-\x{80000000}]+/u", $str, $match);
Получит эту ошибку :
Warning: preg_match(): Compilation failed: character value in \x{...} sequence is too large
Поэтому я не могу сопоставить букву типа 𡃁
с эквивалентным шестнадцатеричным значением f0 a1 83 81
. Вопрос заключается не в том, как сопоставить эти буквы, а о том, как этот диапазон и эта граница исходила из того, что модификатор u
должен обрабатывать строки как UTF-16
PCRE поддерживает UTF-16 с v8.30
echo PCRE_VERSION;
Версия PCRE с PHP 5.3.24 – 5.3.28, 5.4.14 – 5.5.7:
8.32 2012-11-30
Версия PCRE с PHP 5.3.19 – 5.3.23, 5.4.9 – 5.4.13:
8.31 2012-07-06
http://3v4l.org/CrPZ8
Поэтому я не могу сопоставить букву, подобную 𡃁 с эквивалентным шестнадцатеричным значением f0 a1 83 81. Вопрос заключается не в том, как сопоставить эти буквы, а в том, как этот диапазон и эта граница исходила из того, что модификатор u должен обрабатывать строки как UTF-16
Вы смешиваете две концепции, которые вызывают эту путаницу.
F0 A1 83 81
не является шестнадцатеричным значением символа 𡃁. Так UTF-8 кодирует кодовую точку для этого символа в потоке байтов.
Верно, что PHP поддерживает кодовые точки UTF-16 для шаблона \x{}
, но значения внутри {
и }
представляют собой кодовые точки UTF-16, а не фактические байты, используемые для кодирования заданного символа в потоке байтов.
Таким образом, наибольшее возможное значение, которое вы можете использовать с \x{}
на самом деле 10FFFF
.
И для соответствия 𡃁 с PHP вам нужно использовать его кодовую точку, которая, как было предложено @minitech в его комментарии, – \x{0210c1}
.
Дальнейшее объяснение, приведенное в разделе «Действительность строк» из документации PCRE .
Вся строка проверяется перед любой другой обработкой. В дополнение к проверке формата строки проверяется, чтобы все кодовые точки лежали в диапазоне U + 0 до U + 10FFFF, исключая суррогатную область. Так называемые «несимвольные» кодовые точки не исключаются, поскольку в Unicode Corrigendum # 9 четко указано, что их не должно быть.
Символы в «Surrogate Area» Unicode зарезервированы для использования UTF-16, где они используются парами для кодирования кодовых точек со значениями, превышающими 0xFFFF. Кодовые точки, которые кодируются парами UTF-16, доступны независимо в кодировках UTF-8 и UTF-32. (Другими словами, вся суррогатная вещь – это выдумка для UTF-16, которая, к сожалению, запутывает UTF-8 и UTF-32.)
Unicode – это набор символов, который определяет сопоставление от символов к кодовым точкам, а кодировки символов (UTF-8, UTF-16, UTF-32) определяют, как хранить кодовые точки Юникода.
В Unicode символ сопоставляется с одной кодовой точкой, но может иметь различное представление в зависимости от того, как он кодируется.
Я не хочу повторять эту дискуссию снова, поэтому, если вы все еще не совсем поняли об этом, прочитайте «Абсолютный минимум». Каждый разработчик программного обеспечения абсолютно уверен, что должен знать о юникодном и символьном наборах (никаких оправданий!) .
Используя пример в вопросе, 𡃁
сопоставляется с кодовой точкой U+210C1
, но он может быть закодирован как F0 A1 83 81
в UTF-8, D844 DCC1
в UTF-16 и 000210C1
в UTF-32.
Чтобы быть точным, приведенный выше пример показывает, как сопоставить кодовую точку с кодовыми единицами (форму кодировки символов). То, как модули кода сопоставляются с октетной последовательностью, – другое дело. См. Модель кодировки Unicode
Поскольку PHP еще не принял PCRE2 (версия 10.10), цитируемый текст из документации оригинального PCRE.
PCRE включает поддержку 16-разрядной строки в версии 8.30 и 32-разрядную строку из версии 8.32, в дополнение к 8-битной библиотеке по умолчанию.
Помимо поддержки 8-разрядных символьных строк, PCRE также поддерживает 16-разрядные строки (начиная с версии 8.30) и 32-разрядные строки (из версии 8.32) с помощью двух дополнительных библиотек. Они могут быть построены, а также, а не 8-битная библиотека. […]
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.
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)
, (*UTF8)
, (*UTF16)
, (*UTF32)
1 или параметров компиляции PCRE_UTF8
, PCRE_UTF16
, PCRE_UTF32
), все последовательности блоков данных интерпретируются как последовательности символов Юникода, которые состоят из всех кодовых точек от 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.
Функции PCRE на PHP реализуются оболочкой, которая переводит флагов и вызовы PHP в API PCRE (как видно из ветки PHP 5.6.10).
Исходный код вызывается в 8-битный API-интерфейс PCRE ( pcre_
), поэтому любая строка, переданная в функцию preg_
интерпретируется как последовательность 8-разрядных блоков данных (байты). Поэтому, даже если построены 16-разрядные и 32-разрядные библиотеки PCRE, они не доступны через API на стороне PHP вообще.
В результате функции PCRE в PHP ожидают:
Это объясняет поведение, наблюдаемое в вопросе:
u
) максимальное значение в шестнадцатеричной escape-последовательности регулярного выражения представляет собой FF (как показано в [\x{00}-\x{ff}]
) \x{7fffffff}
) в шестнадцатеричной escape-последовательности регулярного выражения, просто не имеет смысла. Этот пример кода демонстрирует:
// 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_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. Если на входе нет других двоичных данных, настоятельно рекомендуется всегда включать режим u
. Шаблон имеет доступ ко всем средствам для правильного соответствия символов Юникода, и как вход, так и шаблон проверяются как действительные строки UTF.
Опять же, используя пример 𡃁
, приведенный выше пример показывает два способа задания регулярного выражения:
"/\xf0\xa1\x83\x81+/u" "/𡃁+/u"
Первый метод не работает с одиночной кавычкой – поскольку escape-последовательность \x
не распознается в одинарной \xf0\xa1\x83\x81+
, библиотека получит строку \xf0\xa1\x83\x81+
, которая в сочетании с режимом UTF будет соответствовать U+00F0 U+00A1 U+0083
а затем U+0081
повторяется один или несколько раз. Кроме того, это также путает следующего человека, читающего код: как они должны знать, что один символ Unicode повторяется один или несколько раз?
Второй метод работает хорошо, и его можно даже использовать с одной кавычкой, но вам нужно сохранить файл в кодировке UTF-8, особенно в случае с символами типа ÿ
, поскольку символ также действителен в однобайтовой кодировке. Этот метод является опцией, если вы хотите совместить один символ или последовательность символов. Однако, как конечные точки диапазона символов, может быть неясно, к чему вы пытаетесь соответствовать. Сравните az
, AZ
, 0-9
, א-ת
, в отличие от 一-龥
(который соответствует большинству блоков CJK Unified Ideographs (4E00-9FFF), за исключением неназначенных кодовых точек в конце) или 一-十
(который является неправильная попытка сопоставить китайские символы для числа от 1 до 10).
Третий метод заключается в том, чтобы напрямую указать точку кода в шестнадцатеричном обращении:
"/\x{210C1}/u" '/\x{210C1}/u'
Это работает, когда файл сохраняется в любой кодировке, совместимой с ASCII, работает как с одиночной, так и с двойной кавычкой, а также дает четкую кодовую точку в диапазоне символов. Этот метод имеет недостаток, заключающийся в том, что он не знает, как выглядит персонаж, и его также трудно читать при указании последовательности символов Юникода.
Как предполагает minitech в первом комментарии, вы должны использовать код – для этого символа это \x{210C1}
. Это также закодированная форма в UTF-32. F0 AF AB BF
– кодированная последовательность UTF-8 (см. http://www.unicode.org/cgi-bin/GetUnihanData.pl?codepoint=210C1 ).
Существуют несколько версий PCRE, где вы можете использовать значения до \x{7FFFFFFF}
. Но я действительно не знаю, что с ним можно сравнить.
Процитировать http://www.pcre.org/pcre.txt :
В режиме UTF-16 код символа является Unicode в диапазоне от 0 до 0x10ffff , за исключением значений в диапазоне от 0xd800 до 0xdfff, поскольку они являются «суррогатными» значениями, которые используются парами для кодирования значений больше 0xffff.
[…]
В режиме UTF-32 код символа является Unicode в диапазоне от 0 до 0x10ffff , за исключением значений в диапазоне от 0xd800 до 0xdfff, поскольку они являются «суррогатными» значениями, которые плохо сформированы в UTF-32.
0x10ffff
– это самое большое значение, которое вы можете использовать для сопоставления персонажа (вот что я извлекаю из этого). 0x10ffff
в настоящее время также является самой большой точкой кода, определенной в стандарте Unicode (см. Раздел «Что такое различия между UTF?» ), Поэтому каждое значение выше не имеет никакого смысла (или я просто не получаю его) …
Я не уверен в 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) )
«но хочу знать о max hex-границе в регулярном выражении»: * во всех utf-режимах: 0x10ffff * собственный 8-бит режим: 0xff * собственный 16-разрядный режим: 0xffff * собственный 32-разрядный режим: 0x1fffffff