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

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 2010

А разве для strcmp в UTF-8 было бы недостаточно тупо сравнить строки побайтно, т. е. той же strcmp?

Евгений Степанищев (bolknote.ru) 2010

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

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

Евгений Степанищев (bolknote.ru) 2010

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

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

SiMM 2010

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

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

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

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

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

Евгений Степанищев (bolknote.ru) 2010

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

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

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

SiMM 2010

все символы (ну или многие) во всех алфавитах в Unicode расположены по алфавиту

Нет, разумеется.

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

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

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

http://ru.wikipedia.org/wiki/%D0%A7%D1%83%D0%B2%D0%B0%D1%88%D1%81%D0%BA%D0%B8%D0%B9_%D0%B0%D0%BB%D1%84%D0%B0%D0%B2%D0%B8%D1%82#.D0.A7.D1.83.D0.B2.D0.B0.D1.88.D1.81.D0.BA.D0.B8.D0.B9_.D0.B0.D0.BB.D1.84.D0.B0.D0.B2.D0.B8.D1.82_.D0.BD.D0.B0_.D0.BE.D1.81.D0.BD.D0.BE.D0.B2.D0.B5_.D0.BA.D0.B8.D1.80.D0.B8.D0.BB.D0.BB.D0.B8.D1.86.D1.8B
http://ru.wikipedia.org/wiki/%D0%A7%D1%83%D0%B2%D0%B0%D1%88%D1%81%D0%BA%D0%B8%D0%B9_%D0%B0%D0%BB%D1%84%D0%B0%D0%B2%D0%B8%D1%82#.D0.9A.D0.BE.D0.B4.D0.B8.D1.80.D0.BE.D0.B2.D0.BA.D0.B8

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

SiMM 2010

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

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

Ну и что?

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

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

Евгений Степанищев (bolknote.ru) 2010

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

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

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

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

SiMM 2010

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

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

SiMM 2010

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

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

Евгений Степанищев (bolknote.ru) 2010

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

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

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

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

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

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

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

SiMM 2010

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

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

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

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

Евгений Степанищев (bolknote.ru) 2010

Я сейчас собрал debian-пакет с intl и попробовал раскатать его на машине с PHP 5.2.4, работает нормально.