Транслитерация URL-адреса: задачи и решения
Эта статья о том, что такое «транслитерация URL-адреса», зачем она нужна и какие её виды нами используются. В статье рассказывается про «взаимно-однозначный транслит», а также приводятся другие интересные наборы правил для транслитерации адресов формата URL.
1. О чём речь
Почти всем из нас, кто решал проблему автоматической или полу-автоматической генерации «удобных для человека URL-адресов» (их ещё называют "ЧПУ" или pretty-urls), приходилось сталкиваться с задачей так называемой «транслитерации URL-адреса».
Эта задача имеет следующие ограничения и стартовые условия:
- URL-адреса страниц могут состоять из ограниченного набора символов: цифр
0–9
, латинских буквa-z
иA-Z
, слэша/
, тильды~
и следующих символов, указанных в RFC 2396:-_.!*'()
- То, из чего мы должны программным путём сгенерировать URL — как правило, заголовок новости. Или документа. Словом, может включать и русские буквы, и знаки препинания, и почти всё, что угодно, включая HTML-таги.
Если разобраться, то перед нами встаёт не одна задача, а целых три:
- Получение красивого читаемого адреса URL — задача, в которой мы жертвуем дотошностью в угоду красивости.
- Формирование короткого однозначного идентификатора (мы его привыкли называть «супертагом») — задача, в которой мы жертвуем красивостью ради борьбы с опечатками.
- Передача англо-русскоязычного слова (или фразы) через URL без потерь — для последующего восстановления исходного «текста» (слова или фразы). Именно эта задача и получила название «взаимно-однозначный транслит», и жертвует красивостью ради однозначности преобразования.
Для решения этих трёх разных задач мы разработали три разных способа транслитерации. Кроме того, мы разработали пару классов на языках PHP и JavaScript, осуществляющих все эти три способа. Ссылку на этот проект с открытым исходным кодом вы найдёте в самом низу документа.
2. URL Translit: Красивый читаемый URL-адрес
Красивый читаемый адрес нам нужно формировать автоматически для новостей, документов, статей, — как правило, на основе заголовка, формат которого мы условно называем «что-угодно». Т.е. там нам могут встретиться не только недопустимые в URL русские буквы, но ещё и знаки препинания.
Нам нужно добиться от автоматики такого преобразования, чтобы результат легко читался, не вызывая затруднений и недоумений. Такое преобразование мы называем «урл-транслит», а его результат, обычно, pretty_url
.
2.1. Постановка задачи
Из чего-угодно™ получать валидный урл, URL, который легко «прочитать» — понять содержимое, глядя на него в адресной строке или в строке, теле страницы или статус-баре.
Не требуется восстановление исходного чего-угодно™.
2.2. Критерии, которые актуальны для данной задачи
Ц
проще воспринимается как ts
, чем как c
);«похожесть» на ГОСТ (но отступления там, где простота важнее);
спецсимволы не имеют значения, а разделители (пробелы, например) — имеют;
разницы между
Е
и Ё
в таких адресах быть не должно;все буквы в нижнем регистре, потому что это не вызывает неоднозначности (и, как показал наш опрос — больше нравится людям);
буквы латинского алфавита не должны пострадать.
2.3. Решение
Решение реализовано на двух языках программирования (PHP и JavaScript), содержится в открытом проекте Translit.
Метод называется UrlTranslit()
.
3. Supertag Translit: Короткий однозначный идентификатор
Если то, о чём мы говорили раньше, своей первой и наиглавнейшей задачей ставило «понятность» адреса для читателя-человека, то задача номер два — уметь формировать адреса-идентификаторы (т.е. такие, по которым мы будем находить, скажем, в БД ту новость, которую следует показать).
Адрес-идентификатор в виде числа часто недостаточен для читателя-человека (например: http://somesite.ru/page/34722 ), поэтому его можно сопровождать «красивым и понятным» дополнением, которое наш программный механизм будет игнорировать (например, страница с адресом http://www.npj.ru/kuso/259318_dve_trenirovki_podrjad показывает то же, что и http://www.npj.ru/kuso/259318 ).
Однако, часто такое решение недостаточно, — ставится дополнительное условие «никаких цифр». Тогда нам ничего не остаётся, как использовать «красивое и понятное» дополнение в качестве идентификатора. В таком случае нам хочется обезопаситься хотя бы от того, что при переписывании забудут написать подчёркивание _
или напишут два подчерка вместо одного. Длину ключа тоже хочется сделать поменьше — дабы хоть немного ускорить поиск/сравнение строк. Такой ключ мы привыкли называть супертаг — supertag
.
3.1. Постановка задачи
Из чего-угодно™ получить максимально короткий идентификатор, который не не теряет смысла/однозначности и вместе с тем некритичен к лишним разделителям и большим/малым буквам.
Не требуется восстановление исходного чего-угодно™.
3.2. Критерии, которые актуальны для данной задачи
Supertag(UrlTranslit(x))
должен быть равен Supertag(x)
,иными словами, наше «укорачивающее» преобразование должно выдавать одинаковый результат, как на исходной строке, так и на «красивом URL», полученном из исходной строки;
преобразование должно давать как можно меньше случайных совпадений результата (ошибок 1 рода);
буквы латинского алфавита не должны пострадать.
3.3. Решение
Решение реализовано на двух языках программирования (PHP и JavaScript), содержится в открытом проекте Translit.
Метод называется Supertag()
.
4. BiDi Translit: Взаимно-однозначный транслит
В качестве исторического экскурса можно заметить, что вся эта «эпопея» с транслитерацией началась в своё время с постановки задачи «взаимно-однозначного транслита» — когда возникла необходимость передавать в URL-совместимом формате информацию таким образом, чтобы её можно было однозначно восстановить обратным преобразованием.
Передаваемая информация включала в себя только буквы, цифры, дефис и «слэш» /
. Это — именно та функциональность, которая используется в НПЖ при формировании ссылок на создание новых страниц. Для формирования «красивых» ссылок на уже существующие страницы используется «урл-транслит», а в БД хранятся «супертаги».
4.1. Постановка задачи
Из строки, содержащей русские и английские буквы, получать валидный URL (без использования %AB
-комбинаций), таким образом, чтобы затем было возможно восстановить оригинальную строку. Также уметь производить и обратную операцию.
В отличие от предыдущих рассмотренных способов здесь требуется восстановление исходной строки (допустима потеря запятых и прочих «слабозначащих» символов).
4.2. Критерии, которые актуальны для данной задачи
русские и латинские буквы не должны пострадать в результате преобразования и последующего восстановления;
регистр букв не должен пострадать в результате преобразования и последующего восстановления;
желательно добиться максимальной читабельности «преобразованной» строки.
4.3. Идея решения
В отличие от предыдущих способов здесь, кроме банальной таблицы транслитерации (приведённой чуть ниже) использовано некоторое алгоритмически-изобретательское решение, идею которого мы сейчас осветим.
Идея решения заключается в подходе к преобразуемой строке как к чередующейся последовательности кириллических и английских «подстрок»:
ThisIsРусскийName/ДляВас/ДемонстрацияOfSwitching |
красным — кириллические.
Очевидно, что каждая чётная подстрока в этом случае — точно «кириллическая», а каждая нечётная — «англоязычная». Из способа деления на подстроки следует, что в «кириллической» не встречаются буквы a-zA-Z
.
Если теперь мы вставим в строку «разграничители» подстрок, то смело можем перевести все «кириллические» строки в алфавит a-z
с помощью любого взаимно-однозначного преобразования, например, ГОСТ.
Наш пример тогда будет выглядеть так:
ThisIs+Russkijj+Name/+DljaVas+/+Demonstracija+OfSwitching |
обратите внимание, что
/
считается «англоязычным» символом.
Осталось решить вопрос с теми заглавными русскими буквами, что после транслитерации занимают более одного символа — мы их переводим как Ж
— Zh
, потому что так «читабельнее». Кроме того, есть некоторый нюанс с твёрдыми и мягкими знаками, пробелом и «экранированием» плюса. Все эти нюансы освещены в сводных таблицах, приведённых ниже.
4.4. Решение
Решение реализовано на двух языках программирования (PHP и JavaScript), содержится в открытом проекте Translit.
Метод называется BiDiTranslit()
.
5. Важное примечание
Все функции/способы транслитерации должны уметь (и умеют) работать в двух режимах:
/
, которые встретятся во входной строке;пропускать сквозь себя все эти слэши неизменными.
Это необходимо для двух разных способов их использования: а) генерирования «имён папок» в виртуальном «дереве сайта», б) генерирования полных адресов/путей (как это делает WackoWiki или НПЖ).
6. Сводные таблицы
Все правила, по которым производится транслитерация URL-адресов, вынесены нами в отдельный документ ConversionTables, чтобы не затруднять чтение статьи.
7. Авторы и благодарности
Материал для этой статьи подготовлен Романом Ивановым и Кусо Мендокуси:.
Библиотеки translit.php
и translit.js
написаны Романом Ивановым и Кусо Мендокуси: соответственно и отлажены коллективным разумом. Test suite для обеих библиотек подобран коллективным разумом, а организован Романом Ивановым.
Текст этой статьи написан Кусо Мендокуси:, стёрт Кусо Мендокуси:, заботливо сохранён Романом Ивановым, восстановлен и переписан заново Кусо Мендокуси: и бережно вычитан Романом Ивановым.
Мы очень благодарны нам за весь этот цирк.
Ещё мы благодарны всем, спрашивавшим нас о «взаимно-однозначном транслите».
8. Ссылки
- RFC 2396. http://www.ietf.org/rfc/rfc2396.txt
- ГОСТ 16876–71. http://spelling.spb.ru/spell057.htm
- Опрос стиля ссылок в ваке. http://web.archive.org/web/200[...]as/OprosStiljaSsylok
- ЧПУ. http://spectator.ru/technology/php/user_friendly_urls
- Translit PHP+JS Classes. http://pixel-apes.com/translit
- Таблицы транслитерации. ConversionTables