Транслитерация URL-адреса: задачи и решения

Эта статья о том, что такое «транслитерация URL-адреса», зачем она нужна и какие её виды нами используются. В статье рассказывается про «взаимно-однозначный транслит», а также приводятся другие интересные наборы правил для транслитерации адресов формата URL.



1. О чём речь


Почти всем из нас, кто решал проблему автоматической или полу-автоматической генерации «удобных для человека URL-адресов» (их ещё называют "ЧПУ" или pretty-urls), приходилось сталкиваться с задачей так называемой «транслитерации URL-адреса».


Эта задача имеет следующие ограничения и стартовые условия:

  1. URL-адреса страниц могут состоять из ограниченного набора символов: цифр 0–9, латинских букв a-z и A-Z, слэша /, тильды ~ и следующих символов, указанных в RFC 2396: -_.!*'()
  2. То, из чего мы должны программным путём сгенерировать URL — как правило, заголовок новости. Или документа. Словом, может включать и русские буквы, и знаки препинания, и почти всё, что угодно, включая HTML-таги.

Если разобраться, то перед нами встаёт не одна задача, а целых три:

  1. Получение красивого читаемого адреса URL — задача, в которой мы жертвуем дотошностью в угоду красивости.
  2. Формирование короткого однозначного идентификатора (мы его привыкли называть «супертагом») — задача, в которой мы жертвуем красивостью ради борьбы с опечатками.
  3. Передача англо-русскоязычного слова (или фразы) через 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. Ссылки