Регулярные выражения для выделения ссылок

Недавно потребовалось сделать автоматическое обрамление ссылок в тег <a>. Хотя на первый взгляд, задача совершенно тривиальная, обнаружилось несколько подводных камней. Во-первых, не все пользователи указывают протокол (http:// или https:// в ссылках). Во-вторых, требовалось распознавать ссылки не только на домен, но и на конкретную страницу сайта или вовсе содержать параметры после знака ?. В-третьих, распознавать кириллические домены. Кроме этого, нужно было сделать так, чтобы уже корректно обрамленная в тег ссылка не обрамлялась повторно, а также обеспечить безопасность, предусмотрев защиту от XSS, а также свести к минимуму ложные срабатывания.

Исходя из этих требований, были приняты следующие решения:



  1. распознавать только те ссылки, перед которыми стоит пробельный символ
  2. распознавать ссылки только в указанном наборе доменов
  3. после домена допускается либо символ /, либо ?, далее ссылкой считается все, что идет до первого пробельного символа или символа " (последнее сделано в целях защиты от XSS)
  4. ссылки с http и без него распознаются отдельными выражениями, также отдельными выражениями распознаются кириллические домены.


И в итоге получился следующий код:

function process_links($text) {
$domains = 'aero|biz|com|edu|gov|info|int|mobi|name|net|org|pro|tel|travel|online|guru|club|ru|su|moscow|eu|ua|com\\.ua|kz|kg|by|uz|ge|az|am|co\\.il'; // список доменов верхнего уровня, которые распознаем
$text=preg_replace('/(^|\s)((www\.)?[\w.\-]+\.('.$domains.')([\/?][^\s"]+)?)(\s|$)/is','$1<a href="http://$2">$2</a>$6',$text);
$text=preg_replace('/(^|\s)((https?:\/\/)?[\w.\-]+\.('.$domains.')([\/?][^\s"]+)?)(\s|$)/is','$1<a href="$2">$2</a>$6',$text);
$text=preg_replace('/(^|\s)([а-яА-Я0-9\.\-]+\.(рф|РФ|москва|МОСКВА|бел|БЕЛ)([\/?][^\s"]+)?)(\s|$)/isu','$1<a href="http://$2">$2</a>$5',$text);
$text=preg_replace('/(^|\s)(https?:\/\/[а-яА-Я0-9\.\-]+\.(рф|РФ|москва|МОСКВА|бел|БЕЛ)([\/?][^\s"]+)?)(\s|$)/isu','$1<a href="$2">$2</a>$5',$text);
return $text;
}

Из любопытных особенностей кода можно отметить обработку ситуаций, когда гиперссылка стоит в начале или конце текста: оказывается, спецсимволы ^ и $ можно использовать в выражениях, предполагающих выбор из нескольких вариантов через символ | как самые обычные символы. Для меня это было открытием! Также для ускорения работы данной функции можно поступить следующим образом: объявить выражения для поиска и замены в виде двух массивов и сделать вызов preg_replace всего один раз, передав ему эти массивы в качестве первых двух параметров.