В Японском языке 3 алфавита, что делает работу с текстом не такой простой, как в европейских языках. Начнем с самого простого и перейдем к более сложному.
Как и в России, в Японии остались сайты с локальной кодировкой, переводим текст в UTF-8:
$output = iconv('Shift-JIS', 'UTF-8//IGNORE', $input);
Есть такая особенность у японцев: текст может быть записан двумя способами — так называемая «полная длина» (Zen-Kaku) и «половина длины» (Han-Kaku). Японский текст — это Zen-Kaku, европейский текст — это Han-Kaku, но в принципе любой европейский текст может быть записан как Zen-Kaku. Пример: SALE. Но особенно японцы любят зен-каку цифры: 123.
Чтобы перевести SALE в SALE:
public function toHanKaku($text)
{
return mb_convert_kana($text, 'a');
}
Зачем же японцам 3 алфавита? В первом кандзи (??) — записываются иероглифы. Второй — это хирагана (????), это, можно сказать, главный алфавит, потому что любой кандзи может быть записан хираганой. И третий — это катакана (????) и она — это то же самое, что и хирагана, за исключением того, что ею записываются иностранные слова.
Для перевода хираганы в катакану (??? в ???) (одно и то же слово в разных алфавитах) воспользуемся той же функцией, только с другим ключем:
public function toKatakana($text)
{
return mb_convert_kana($text, 'C');
}
Та же самая проблема и с регулярными выражениями. Есть дата в формате: 2016.20.20. Чтобы она распознавалась, обязательное условие модификатор «u» — Unicode.
preg_match('/[\d]{4}.[\d]{2}.[\d]{2}/u', $text, $match);
Как и все, японцы не заморачиваются с адресами страниц и пишут прям в адрес японскими иероглифами, например, так:
<a href="http://ja.wikipedia.org/wiki/???">???</a>
Адрес верный, но чтобы его понимал, например, Curl, он должен быть закодирован:
public function japaneseUrlEncode($text)
{
return preg_replace_callback(
'/[^\x21-\x7e]+/', function($matches) {
return urlencode($matches[0]);
}, $text
);
}
Система ввода японского текста тоже особенная, она позволяет прямо с клавиатуры без дополнительных инструментов добавлять разные украшения: dd??????. Всеми этими штуками буквально переполнены блоги, форумы, твиттеры и тому подобные места.
Проблема же заключается в том, что при попытке вставить некоторые эмодзи в обычную таблицу utf8_general_ci, мы получим обрезанный текст и ошибку:
Warning: #1366 Incorrect string value: '\xF0\x9F\x98\x8ASI...' for column 'content' at row 1
А дело тут в том что utf8_general_ci — это конечно очень богатый набор символов, но он ограничен 3 байтами на символ и не содержит 4-байтовые эмодзи. Кодировка которая подойдет — это utf8mb4.
Пример текстовой красоты по-японски:
??????????SIENA???????????
?????????new?????????d
Но не всем подойдет менять кодировку, есть другое решение — удаляем 4-байтовые эмодзи:
public function clear4byte($string)
{
return preg_replace('%(?:
\xF0[\x90-\xBF][\x80-\xBF]{2} |
[\xF1-\xF3][\x80-\xBF]{3} |
\xF4[\x80-\x8F][\x80-\xBF]{2}
)%xs', '', $string);
}
Надеюсь, данный материал будет полезен тем, кто работает с японским текстом, а также всем остальным для общего развития.
P.S.: Первая публикация не удалась, комментарий модератора:
Ощущение, что статья обрывается где-то посередине. Дополните материал и опубликуйте еще раз.
То, что называется «дежавю» — с хабром произошел как раз случай, описанный в последнем примере, а именно: я добавил для наглядности символы в четырехбайтовой кодировке, что вызвало ошибку, и текст был сохранен в базу данных в обрезанном виде.
Комментарии (27)
gaki
29.06.2016 12:33Боюсь даже представить, насколько данная тема актуальна для основной аудитории :)
Telescopium
01.07.2016 10:16На апворке, к примеру, встречаются задания от американцев, работающих с сайтами с ЮВА
gaki
01.07.2016 11:48?????!(???????)?
mnakamura
29.06.2016 13:26+1Вообще в целом вы затрагиваете очень непростую тему и к ней нельзя относиться так легко — мол, два регэкспа и вперед. Тема связана с нормализацией и сравнением строк в языке, где правила этого самого сравнения сильно отличаются от европейских и ожидания людей тоже отличаются.
Например, огромная туча людей будет справедливо считать, что ?, ??? и ??? (а зачастую и "midori") — это одно и то же. И будут в целом правы. Это примерно как на русском языке считать, что "зелёный", "зеленый" и "ЗЕЛЕНЫЙ" — это одно и то же.
Есть куда более сложные примеры — когда нужно считать фонетические эквиваленты. Например, то же самое ????? люди пишут иногда как ????? ("как слышу — так и пишу") или даже ????, например, пропустив двойную "n".
А иногда, например, когда речь идет об обработке баз данных с именами-фамилиями, так наоборот нельзя делать ни в коем случае, потому что ?? ("защитница утреннего солнца") и ?? ("ясная голова") — две совершенно разных девушки, хотя и то, и другое произносится как "Akiho". И эти случаи надо отдельно выделять и оформлять подобным образом.
Продолжать можно еще очень долго.
Dreyk
29.06.2016 13:51+2Мне чисто ради интереса, «как слышу — так и пишу» — это как «малако» вместо «молоко» или это тоже правильное написание?
Не смог удержатьсяРусский, французский и китайский лингвисты решили написать имена друг друга, каждый на своём языке.
— Моя фамилия Ге, — сказал француз китайцу.
— В китайском языке два иероглифа Ге, но, к сожалению, ни один из них не подходит для фамилии.
— Почему?
— Потому что один имеет значение «колесо», а другой передает звук, с которым лопается мочевой пузырь осла.
— А что плохого в колесе?
— Мужское имя не может быть круглым. Для твоего имени мы возьмем иероглиф Шэ, означающий «клавиатура», «корнеплод», «страница», а также прилагательное «бесснежный» и дополним его иероглифом Нгу, означающим мужской род. В конце я пишу иероглиф Мо — «девственный».
— Но это, мягко говоря, не совсем…
— Никто не будет считать тебя девственником, просто без иероглифа Мо иероглифы Ше-Нгу означают «сбривающий мамины усы».
— Хорошо, теперь я напишу твое имя.
— Моя фамилия Го.
— Отлично, я начну твою фамилию с буквы G.
— Что означает буква G?
— У нас, европейцев, сами по себе буквы ничего не значат, но чтобы проявить к тебе уважение, я поставлю перед G букву H — во французском она все равно не читается.
— Отлично! Дальше O?
— Нет, чтобы показать, что G — произносится как Г, а не как Х, надо после G поставить букву U, а также H — чтобы показать, что U не читается сама по себе, а только показывает, как правильно читать G, и буквы EY, показывающие, что слово не длинное и скоро закончится.
— Hguhey… дальше O?
— Нет, О во французском произносится как А или Ё, в зависимости от стоящих по соседству букв, ударения и времени года. Твое чистое О записывается как AUGHT, но слово не может кончаться на T, поэтому я добавлю нечитаемое окончание NGER. Вуаля!
Русский лингвист поставил бокал на стол, взял листочек и написал «Го» и «Ге».
— И всё?
— Да.
Француз с китайцем почесали в затылке.
— Хорошо, а какая у тебя фамилия?
— Щекочихин-Крестовоздвиженский.
— А давайте просто выпьем? — первым нашёлся китаец.
Русский кивнул и француз с облегчением поднял тост за шипящие дифтонги.mnakamura
29.06.2016 14:06+1Да, примерно так. ????? (konnichiwa) — традиционное японское нейтральное приветствие (типа «добрый день»). Проблема в том, что на записи последний слог формально «ha», а не «wa». Если взять само «konnichi» — это просто «этот день». Частица ? после члена предложения обычно показывает что это слово — подлежащее. И когда ? является именно этой самой частицей, ее надо произносить как «wa», а не «ha». Вообще, в японском очень простые правила чтения (примерно как в русском), исключений по пальцам пересчитать, но вот такое есть.
В обычных предложениях никто массово опечаток не делает (все пишут «ha», произносят «wa» и никакого когнитивного диссонанса не испытывают). С приветствием же проблема в том, что оно как бы полноценным предложением не является и устоялось в такой форме чисто по историческим причинам. Все на автомате произносят и никто даже не задумывается о том, что это значит дословно. И точно так же на автомате иногда пишут. Я в свое время даже по входящей почте статистику считал — на 100 писем с приветствием эдак 5-7 «неверных» записей точно будет. Причем письма вполне от чистокровных японцев, не от каких-нибудь понаехавших студентов.
mnakamura
29.06.2016 14:19+1Кстати, на японском «Щекочихин» отлично записывается как ?????? — «she-ko-chi-hi-n».
Гораздо хуже со всякими там «gasoline» (который выглядят страшно на записи — ????), всякими там фамилиями типа «Волобуев» (которая становится «borobuebu») или с сосредоточением согласных типа «Мкртчян» (с другом могу представить).
Settler1
29.06.2016 14:31Ну скорее как «Здравствуйте» и «Здрасте», то есть не правильно, но в просторечии — применимо.
Telescopium
01.07.2016 09:514-байтовый смайл \xF0\x9F\x98\x8A' можно заменить на 3-байтовый '\xE2\x98\xBA'
но делать это для всех эмодзи ещё тот праздник :)mnakamura
01.07.2016 12:35Они от этого обычно становится не такими цветными. Ну и, да, там их сейчас несколько тысяч, включая всякую платформо-эксклюзивную фигню типа «два мужчины держатся за руки и перед ними мальчик и девочка».
psrafo
Ох, интересно то как, не, серьезно.
mnakamura
Только ошибок и неточностей вагон и маленькая тележка:
Какая же она однобайтовая? Она многобайтовая, как и UTF-8, почти все, кроме ASCII как раз в 2 байта кодируется.
Стоит добавить, что в целом в шрифтах сильно поощряется моноширинность. Hankaku — это по сути те же codepoints, что в ASCII — но за счет форсируемого моноширинного шрифта они становятся ровно в половину знакоместа.
Ларчик просто открывается: во всеми любимой операционной системе они вводятся по умолчанию при включенном Japanese IME. Как и full-width скобочки, точки, двоеточия, вопросительные/восклицательные и всякие такие знаки препинания.
Адрес надо URL-encode'ить в любом случае, японский ли это или нет. И тем более крайне странно использовать такую выборочную функцию.
Из того, что вы привели, ни один символ не является расширенным 4-байтовым emoji. Все это прекрасно кодируется в 3-байтовый UTF-8. Я понимаю, что у вас сохранить не получилось из-за странностей хабрового движка, но зачем народ в заблужение-то вводить?
Settler1
Она действительно 1-2 байтовая. Спасибо что поправили.
Если кодировать весь адрес, то будут закодированы также :// и другие символы, в результате, получим не работающую ссылку.
Более того даже в спецификации этой функции написано что кодироваться должна именно часть, как правило передаваемые параметры: http://php.net/manual/ru/function.urlencode.php Поэтому нужно именно выборочно.
Нельзя. Конечно нельзя. Если адрес символа зарезервирован на 4 байтах, перевести его в 3 байта невозможно. Убрав 1 байт вы получите просто другой символ. Хабр 4 байта не сохраняет.
mnakamura
Но не так же! Собственно, даже в приведенном вами PHP manual написано: разумеется, нужно понимать семантику кодируемого и, как правило, разделять URL на составные части и кодировать по частям. Причем разные части кодируются по разному: домен — в punycode, path — percent-encoding с одним набором «запрещенных» ASCII символов, query — другим, fragment — остается в unicode (если речь не о CURL, скажем, а о каких-то браузерах). То, что предлагаете вы — просто неверно (а местами и опасно). Попробуйте закодировать, скажем, какой-нибудь http://???.jp/ вашей функцией — я посмотрю, что получится.
Это все отлично, только для демонстрации этого вы используете 3-байтовые символы. Я понимаю, что вставить напрямую не удалось — ну так вставьте картинками, или добавьте ссылку на какой-нибудь сервис, корректнее работающий с unicode, или еще что-нибудь. Пока получается, что примеры в статье несколько противоречат содержимому.
Settler1
Ею кодируется строка query, а не url. Это вопрос в том как вы её будите применять.
symbix
В случае с mysql и Shift-JIS, кстати, если не выставлена корректно _клиентская_ кодировка и, соответственно, libmysql производит экранирование в однобайтовом режиме — можно протащить SQL injection, основанный на том, что в Shift-JIS есть вполне валидные символы, где последний байт совпадает с бэкслешом (0x5c).
mnakamura
Это, кстати, обычно не работает, т.к. если libmysql производит экранирование в некоторой кодировке, то почти всегда она же и будет этот запрос интерпретировать, причем в той же кодировке. Может сработать в некоторых экзотических случаях, когда между экранированием и выполнением запроса есть дополнительные стадии, второй раз заменящие кодировку.
symbix
Так в том и дело, что запрос будет интерпретировать сервер. И сработает именно тогда, когда корректно выставили кодировку Shift-JIS для сервере (например, выполнив запрос set names), но не выставили кодировку для клиентской библиотеки, и она считает, что работает с дефолтной latin1.
С UTF-8 никаких проблем при такой некорректной конфигурации заметно не будет, так что подобные уязвимости, проявляющиеся только с «экзотическими» мультибайтными кодировками могут присутствовать в массе продуктов.