В BB-code есть несколько тегов, которые очень капризны, потому что могут быть написаны по-разному. Один из них - тег [url]ссылка[/url]. Попробуем разобраться с ним двумя способами и, конечно, проверим, какой быстрее.
Займемся парсингом bb-code вплотную.
Если вам не интересно читать про то, как парсится тег url - можете сразу взять готовую функцию парсинга bbcode.
Есть текст, в котором присутствуют теги [url] в разных вариантах, а именно:
Еще могут присутствовать один или два пробела вокруг знака равенства. К тому же, http:// может быть указано, а может и нет. Необходимо заменить все эти конструкции ссылками. Если адрес указан внутри [url], а в контейнере [url][/url] содержится текст, нужно сформировать ссылку, ведущую на адрес и содержащую текст. Если адрес указан в контейнере, в тексте ссылки должен быть этот адрес. Кроме всего прочего, хотелось бы защититься от XSS-атак и прочих вживлений кода в страницу.
В качестве текста, для теста, я соорудил нечто невразумительное, но удовлетворяющее условиям задачи. Вот:
$text = q(Некоторый текст с [url=dayte2.com]ссылками[/url] на BBcod'e. Ссылки могут быть разными [url]http://dayte2.com[/url]. Важно защититься от [url=javascript: alert('XSS!');]ссылок[/url] с XSS! Добавим кавычки к [url="dayte2.com"]url[/url]. А теперь укажем [url="http://dayte@2.com"]Неверный url[/url]);
Этот текст охватывает, конечно, не все варианты, которые возможны, но если хотите, можете поиграть с этим текстом:)
Первое, что приходит в голову - сделать большую регулярку, которая будет лопатить весь текст. Однако, появляются трудности. В частности, если не указан http:// внутри [url], нужно дописать его вручную в текст ссылки, а если указан, то этого делатьне надо. Кроме того, если адрес указан внутри [url], то текст контейнера не нужно проверять на корректность адреса, в противном случае, эту проверку делать нужно.
Все вышеперечисленное заставило меня прийти к неутешительному выводу: нужно использовать функцию замены в блоке подстановки в регулярном выражении. Вот регулярка, сдобренная комментариями, которая у меня получилась:
$text =~ s /
\[url #Открываем [url]
(?: #внутри может быть адрес
\s?=\s? #если есть, то там 100% есть =, возможно, окруженное пробелами
(['"]?) #если есть кавычка, запоминаем ее в $1
(?:http:\/\/)? #можно без http:\/\/
( #пишем url в $2
[a-z0-9-.]+\. #домены второго и выше уровней
\w{2,4} #доменная зона
)
\1 #подставляем кавычку, чтобы соблюсти парность
)? #код не обязательно должен быть в [url]
\] #закрыли [url]
(.*?) #запоминаем текст ссылки в $3
\[\/url\]
/
bb_url($1,$2,$3)
/gixe;
В регулярке применяются модификаторы gixe. Поясню что они значат:
Поясню конструкцию (?:что-то). Она всего-лишь захватывает свое содерижимое, как обычные скобки, но, в отличие от них, не пишет свое содержимое в переменную с номером. Это экономит память и время, а так же, не дает запутаться в скобках и номерах переменных.
Полученные переменные $1, $2 и $3, отправляем на растерзание функции bb_url. Вот ее код:
sub bb_url
{
my ($quote, $url, $text, $trash) = @_;
if ($url ne '')
{return qq(<a href="http://$url">$text</a>)}
elsif ($text =~ /^http:\/\/[a-z0-9-.]+\.\w{2,4}$/)
{return qq(<a href="$text">$text</a>)}
}
Решение работает. По крайней мере, мне не удалось сбить его с толку. Однако, встает вопрос: а не проще ли было, прогнать текст через две простые регулярки. Пусть одна заменяет [url] с адресом внутри, а другая - с адресом в блоке. Возможно, это было бы эффективнее. За чем дело стало? Проверим!
Итак, пишем регулярки. Первая заменяет ссылки, с адресом внутри блока.
$text =~ s/\[url\](?:http:\/\/)?([a-z0-9-.]+\.\w{2,4})\[\/url\]/<a href="http:\/\/$1">$1<\/a>/gi;
Здесь, пожалуй, ничего нового нет. Пожалуй, стоит обратить внимание, что, как и в первом решении, http://, при замене, мы дописываем ручками, а не выхватываем его из введенного адреса. Это дает возможность верно интерпретировать даже такой код: [url=dayte2.com]сайт[/url].
Теперь нужно разобраться с [url], где адрес указан внутри открывающего тега.
$text =~ s/\[url\s?=\s?(['"]?)(?:http:\/\/)?([a-z0-9-.]+\.\w{2,4})\1\](.*?)\[\/url\]/<a href="http:\/\/$2">$3<\/a>/gi;
Как и в первом решении, мы учли возможные пробелы вокруг знака равенства и парность кавычек.
Указанное решение, так же работает. При проведенных мною тестах, не было обнаружено никаких расхождений в результатах работы обоих решений. Однако, хотелось бы знать, какое из них эффективнее?
Подход прост, как апельсин. Запомнить время старта и время окончания работы решния, а затем вычесть и узнать время работы. Регулярные выражения Perl работают очень быстро, поэтому даже на довольно большом объеме текста, скорость работы была равна 0. Поэтому пришлось соорудить цикл, в котором много раз переменной $text присваивалось одно и то же значение, а затем, оно разбиралось одним из решений. Я проводил 10000 замен. Полный код решения приводить здесь не буду, покажу лишь псевдокод.
$start = times;
for ($i=0; $i<10000;$i++)
{
устанавливаем $text;
прогоняем $text через регулярку;
}
$end = times;
$diff = $end-$start;
print $diff;
Прилагаю свой скрипт, которым я тестировал эти решения.
В результате тестирований, я получал результаты которые выглядели примерно так:
Большая регулярка: 0.359
Две маленьких: 0.235
Вывод: эффективнее использовать две маленьких регулярки, чем одну навороченную. Черт, а жаль, такая она красивая...
В этой статье мы рассмотрели конкретный пример парсинга текста сайта. Если вы хотите получить что-то подобное, профессионально выполненное на свой сайт - закажите грабер или парсер у меня.