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+, так что имеет смысл использовать именно его.
А разве для strcmp в UTF-8 было бы недостаточно тупо сравнить строки побайтно, т. е. той же strcmp?
Комментарий для SiMM:
Я, честно сказать, пока даже не уверен, что правильно сэмулировал эту функцию. Для этого надо быть уверенным, что все символы (ну или многие) во всех алфавитах в Unicode расположены по алфавиту, а я в этом совсем не уверен.
Комментарий для SiMM:
Ну и побайтово нельзя, конечно. UTF-8 не фиксированного размера. Мало ли какого размера символы нужно будет сравнить.
Комментарий для Евгения Степанищева:
Нет, разумеется. Но перевод в UTF-32 эту проблему никак не решает :)
Не фиксированного. Но символы с бОльшим Unicode имеют одинаково бОльший код как в UTF-32, так и в UTF-8 (в строковом смысле).
Комментарий для SiMM:
Это бессмысленное выражение. В UTF-32 строки ещё как-то можно побайтно сравнивать — там каждый символ занимает 4 байта. В UTF-8 русский символ займёт два байта, английский — один, а арабский — три. При сравнении побайтно такой каши из символов, буду сравниваться не символы, а их произвольные части.
Контрпример:
с точки зрения кодировки — эти буквы находятся далеко в хвосте кириллицы (коды не ниже 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 ;)
Комментарий для Евгения Степанищева:
Ну и что?
А Вы попробуйте сравнить :) При сравнении сразу же определиться, что английский символ идёт раньше русского, и уж тем более — раньше арабского. Что вполне соответствует их кодам в Unicode :) Возможно, что приведённая в вики табличка Вам поможет понять, о чём я.
http://ru.wikipedia.org/wiki/UTF-8
Комментарий для SiMM:
Да не работает это так. Например, знак ударения — отдельный символ в Unicode, он не должен влиять на сравнение. И это только самая простая комбинация, там масса комбинирующих символов. Буква с ударением должна быть равна букве без него. Буква «ё» должна быть раньше «л» и так далее.
Сравнение Unicode выполняется вот так: http://unicode.org/reports/tr10/ Не уверен, что хочу решать эту задачу вообще.
При переводе в UTF-32 он никуда не денется. Т. е. это ничем не лучше, чем вырезать лишнее перед сравнением.
Так я к тому и клоню, что в этом случае вполне достаточно тупо байты сравнить, если уж задачу полностью не решать (если ещё и со всеми языками заморачиваться — трудоёмко, полагаю, получится).
Комментарий для SiMM:
Что-то я не понял к чему контпример. Как видно, я не утверждаю, что символы расположены в Unicode по алфавиту.
А я и не говорил, что мой способ хорош, я говорил, что его ещё надо проверять, чем я из занимался в последнее время.
Я клонил к тому, чтобы отказать от использования strcmp и им подобных в коде, но оказалось, есть решение. См. дополнения к посту.
Хотя бы для остальных читателей :)
Ну я и не утверждал, что Вы утверждали — всего лишь привёл пример, подтверждающий, что Ваша неуверенность — не беспочвенна. Правда, про более простой пример с ё я почему-то не подумал :)
Я сейчас собрал debian-пакет с intl и попробовал раскатать его на машине с PHP 5.2.4, работает нормально.