PHP и UTF-8: четыре с половиной или некоторые функции

Я пока приостановил эпопею с UTF-8, так как сменились приоритеты, но обязательно ещё вернусь к этому вопросу. А пока, в паузах, потихоньку пишу для функций, для которых уже нет UTF-8-аналогов эти самые аналоги. Вот, например, очередной набор (это статические методы класса UTF, для краткости я убираю часть для работы с однобайтными строками):

public static function strcmp($str1, $str2) {
    // используется модуль intl
    return collator_compare(collator_create(''), $str1, $str2);
}

public static function strncmp($str1, $str2, $len) {
    return self::strcmp(self::substr($str1, 0, $len), self::substr($str2, 0, $len));
}

public static function strcasecmp($str1, $str2) {
    return self::strcmp(self::strtolower($str1), self::strtolower($str2));
}

static public function ctype_space($text) {
    return preg_match('/^\p{Zs}+$/us', $text);
}

Привожу в качестве иллюстрации того, что не всё так сложно как кажется. Стоит только сесть и начать.

Некоторые функции в PHP уже есть (mb_substr), какие-то требуют минимальных доделок (preg_replace), некоторые можно выразить через другие (trim через ltrim и rtrim), оставшиеся, конечно, придётся написать (strcmp, ltrim, addcslashes и так далее).

И, кстати, в ходе родился замечательный совет: если функцию трудно реализовать (например, str_ireplace), посмотрите, может проще переписать место, где она используется. У меня две функции были использованы ровно по одному разу.

Добавлено позднее: тщательно изучил вопрос сравнения Unicode, я выяснил, что производить сравнение так, как это сделал я нельзя, поэтому функцию strcmp я удалил, буду переписывать. Например, буква «ё» в Unicode стоит дальше «л», а должно быть наоборот.

Добавлено ещё позже: в PHP 5.3 появилось расширение Intl, основанное на известной библиотеке UCI, оказывается это расширение собирается и с PHP 5.2.4+, так что имеет смысл использовать именно его.

Комментарии 13

SiMM № 1
А разве для strcmp в UTF-8 было бы недостаточно тупо сравнить строки побайтно, т.е. той же strcmp?
Евгений Степанищев (bolknote.ru) автор № 2
Я, честно сказать, пока даже не уверен, что правильно сэмулировал эту функцию. Для этого надо быть уверенным, что все символы (ну или многие) во всех алфавитах в Unicode расположены по алфавиту, а я в этом совсем не уверен.
Евгений Степанищев (bolknote.ru) автор № 3
Ну и побайтово нельзя, конечно. UTF-8 не фиксированного размера. Мало ли какого размера символы нужно будет сравнить.
SiMM № 4

Комментарий для Евгения Степанищева:

Для этого надо быть уверенным, что все символы (ну или многие) во всех алфавитах в Unicode расположены по алфавиту, а я в этом совсем не уверен.

Нет, разумеется. Но перевод в UTF-32 эту проблему никак не решает :)

Ну и побайтово нельзя, конечно. UTF-8 не фиксированного размера. Мало ли какого размера символы нужно будет сравнить.

Не фиксированного. Но символы с бОльшим Unicode имеют одинаково бОльший код как в UTF-32, так и в UTF-8 (в строковом смысле).

Евгений Степанищев (bolknote.ru) автор № 5

Комментарий для SiMM:

Но символы с бОльшим Unicode имеют одинаково бОльший код как в UTF-32, так и в UTF-8 (в строковом смысле)

Это бессмысленное выражение. В UTF-32 строки ещё как-то можно побайтно сравнивать — там каждый символ занимает 4 байта. В UTF-8 русский символ займёт два байта, английский — один, а арабский — три. При сравнении побайтно такой каши из символов, буду сравниваться не символы, а их произвольные части.

SiMM № 6
> все символы (ну или многие) во всех алфавитах в Unicode расположены по алфавиту
Нет, разумеется.

Контрпример:

Кириллический алфавит чувашского языка … Включает 33 буквы русского алфавита и 4 дополнительные буквы кириллицы — Ӑ ӑ, Ӗ ӗ, Ҫ ҫ , Ӳ ӳ

с точки зрения кодировки — эти буквы находятся далеко в хвосте кириллицы (коды не ниже 1194, в то время как у буквы я код 1103), а вот в алфавите они располагаются совсем иначе

http://ru.wikipedia.org/wiki/%D0%A7%D1%83% … 1.86.D1.8B
http://ru.wikipedia.org/wiki/%D0%A7%D1%83% … 0.BA.D0.B8

Вообще, если глубоко копать, можно довольно глубоко закопаться :) Вплоть до написания функций для правильной сортировки в MySQL ;)

SiMM № 7

Комментарий для Евгения Степанищева:

В UTF-32 строки ещё как-то можно побайтно сравнивать — там каждый символ занимает 4 байта.

Ну и что?

В UTF-8 русский символ займёт два байта, английский — один, а арабский — три. При сравнении побайтно такой каши из символов, буду сравниваться не символы, а их произвольные части.

А Вы попробуйте сравнить :) При сравнении сразу же определиться, что английский символ идёт раньше русского, и уж тем более — раньше арабского. Что вполне соответствует их кодам в Unicode :) Возможно, что приведённая в вики табличка Вам поможет понять, о чём я.
http://ru.wikipedia.org/wiki/UTF-8

Евгений Степанищев (bolknote.ru) автор № 8

Комментарий для SiMM:

При сравнении сразу же определиться, что английский символ идёт раньше русского, и уж тем более — раньше арабского.

Да не работает это так. Например, знак ударения — отдельный символ в Unicode, он не должен влиять на сравнение. И это только самая простая комбинация, там масса комбинирующих символов. Буква с ударением должна быть равна букве без него. Буква «ё» должна быть раньше «л» и так далее.

Сравнение Unicode выполняется вот так: http://unicode.org/reports/tr10/ Не уверен, что хочу решать эту задачу вообще.

SiMM № 9
Например, знак ударения — отдельный символ в Unicode, он не должен влиять на сравнение.

При переводе в UTF-32 он никуда не денется. Т.е. это ничем не лучше, чем вырезать лишнее перед сравнением.

SiMM № 10
Не уверен, что хочу решать эту задачу вообще.

Так я к тому и клоню, что в этом случае вполне достаточно тупо байты сравнить, если уж задачу полностью не решать (если ещё и со всеми языками заморачиваться — трудоёмко, полагаю, получится).

Евгений Степанищев (bolknote.ru) автор № 11

Комментарий для SiMM:

> Для этого надо быть уверенным, что все символы (ну или многие) во всех алфавитах в Unicode расположены по алфавиту
Контрпример

Что-то я не понял к чему контпример. Как видно, я не утверждаю, что символы расположены в Unicode по алфавиту.

При переводе в UTF-32 он никуда не денется. Т.е. это ничем не лучше, чем вырезать лишнее перед сравнением.

А я и не говорил, что мой способ хорош, я говорил, что его ещё надо проверять, чем я из занимался в последнее время.

Так я к тому и клоню, что в этом случае вполне достаточно тупо байты сравнить, если уж задачу полностью не решать

Я клонил к тому, чтобы отказать от использования strcmp и им подобных в коде, но оказалось, есть решение. См. дополнения к посту.

SiMM № 12
Что-то я не понял к чему контпример.

Хотя бы для остальных читателей :)

Как видно, я не утверждаю, что символы расположены в Unicode по алфавиту.

Ну я и не утверждал, что Вы утверждали — всего лишь привёл пример, подтверждающий, что Ваша неуверенность — не беспочвенна. Правда, про более простой пример с ё я почему-то не подумал :)

Евгений Степанищев (bolknote.ru) автор № 13
Я сейчас собрал debian-пакет с intl и попробовал раскатать его на машине с PHP 5.2.4, работает нормально.