Практическое использование операторов m// и s///
5.6.1. Как укоротить длинные URL и длинные слова?
У вебмастеров иногда возникает задача укоротить длинные URL, которые вводят в форму участники форумов. Из-за длинных ссылок расползается дизайн страниц. Предположим, кто-то отослал на форум такой текст:
Рекомендую посетить: http://www.lsafhwoeufqfsjvdkghalhasdghasglhasgl.com/fasfasfasdf/
На самом деле ссылка может оказаться еще длинее. В HTML-коде страницы форума эта ссылка должна появиться в виде URL:
Рекомендую посетить: <a href="http://www.lsafhwoeufqfsjvdkghalhasdghasglhasgl.com/fasfasfasdf/" target="_blank"> http://www.lsafhwoeufqfsjvdkghalhasdghasglhasgl.com/fasfasfasdf/</a>
Внутри тега a ссылку, естественно, обрезать не надо, тем более, что в видимом тексте ее не будет, а вот то, что стоит перед тегом </a>, будет видно, и если это "слово" будет слишком длинным, то это может испортить дизайн. Надо после 50-го символа такой ссылки вставить три точки и получить такой текст:
Рекомендую посетить: <a href="http://www.lsafhwoeufqfsjvdkghalhasdghasglhasgl.com/fasfasfasdf/" target="_blank"> http://www.lsafhwoeufqfsjvdkghalhasdghasglhasgl.co…</a>
Другие длинные слова (не ссылки) надо также урезать. Мало ли что может ввести пользователь… Я нашел такое решение:
s/(\A|\s)(?!href=")(\S{50})\S+/$1$2.../g;
Предполагаем, что текст находится в переменной $_. Регулярное выражение начинает поиск от начала текста и каждого пробельного символа. Если после этого не стоит фрагмент href=, то проверяется, есть ли после этого "слово" длиной 50 символов, после которого стоит непробельный символ. Если да, то происходит замена всего, что найдено на $1 - пробельный символ перед длинным "словом", 50 символов с начала этого "слова" на $1, эти же 50 символов с начала длинного "слова", а последующие непробельные символы заменяются на три точки. Если бы в начале шаблона вместо (\A|\s) стояло просто (\s), то длинное "слово", стоящее в самом начале текста, не было бы укорочено.
Вот еще один вариант с использованием кода Perl в части замены:
s/((\S{50})\S+)/index($1,'href=') > -1 ? $1 : "$2..."/ge;
Ищем 50 непробельных символов, сразу за которыми есть хотя бы один непробельный символ. Эти 50 символов берем в переменную $2, а все длинное "слово" берем в переменную $1. В части замены проверяем, есть ли фрагмент текста href= в перемнной $1.
Если он есть, то заменяем найденное на $1, т.е. на себя, а если нет, то заменяем найденное на текст $2, за которым идут три точки.
Это был практический пример задачи, которую я решал по просьбе одного вебмастера.
Предположим, мы хотим узнать, есть ли в данном тексте непробельный символ между тегами. Предполагается, что теги правильно закрыты и нетеговых символов < и > не встречается. Вот первое решение:
$_=' <pppp>' x 13000; $_.='<table>a'; if (/[^\s<>](?![^<>]*>)/g) { print "1\n".pos; } else { print "0\n"; }
Вначале мы создаем длинную строку (больше 90000 символов) из тегов, в конец которой вставляем букву a вне тегов. В регулярном выражении мы ищем символ, отличный от пробельного и символов < и >, который не находится внутри тега. "Не находится внутри тега" означает, что сразу после этого символа не должно быть текста, соответствующего шаблону [^<>]*>. В случае нахождения такого символа программа печатает единицу и смещение этого символа от начала текста.
Вот другое решение, которое в отличие от первого настроено быстро пропускать все символы, которые нас не интересуют, т.е. пробельные и теги <> вместе с их содержимым:
/\A(?>(?:(?>\s*)<(?>[^>]*)>)*)\S/g
Оно кажется сложным из-за наличия трех атомарных группировок. Вот этот же шаблон без них:
/\A(?:\s*<[^>]*>)*\S/g
Внутри скобок мы пропускаем последовательности пробельных символов \s* и конструкции <[^>]*>. Обратите внимание, что между этими подшаблонами излишне ставить знак альтернативы |. Вначале стоит привязка \A, чтобы поиск не начался внутри тега.
Какой метод поиска выбрать, чтобы скорость работы программы была максимальной, - искать ли нужный фрагмент или быстро пропускать все до него, - зависит от того, насколько плотно сидят нужные фрагменты в тексте.