Дано: текст с кривым-кривым и неправильным вообще html.
Задача: расставить в этом html ссылки на заданный адрес по заданному слову. Сложность в том, что если это слово стоит в каком-то контейнерном теге, кроме <p>, его трогать не надо.
Если бы можно было трогать слово где угодно, задача решалась бы элементарно. Допустим, мы хотим чтобы каждое вхождение слова "мама" вело на главную страницу нашего сайта. Но это слово встречается с оформлением и мы не хотим портить его. То есть, нужно делать ссылкой только те слова "мама", которые не находятся в потенциально "опасных" тегах, которые могут менять его форматирование.
Например, текст:
<p><span>Мама</span> мыла раму.</p>
<p>Рама вымыта by мама.</p>
Подразумевает, что в ссылку должна превратиться только вторая мама. Надеюсь, понятно объяснил задачу.
Попытки решения при помощи регулярных выражений не увенчались успехом. Слишком сложно.
Пробовал разбивать на токены на лету и заменять по ходу жизни. Тоже не получилось.
Думал уже написать парсер токенов, но получилось все несколько проще.
sub setLinks {
my $out = shift;
my $out_new='';
my $last=$out; #на случай, если ничего заменяться не будет и абцазев нет
my $num=0;
my @tokens;
while($out =~ /(.*?)(<p(?: [^>]*?)?>)(.*?)(<p>|<\/p>|$)/sgi) { #разбиваем текст на абцазы
my $innerP=$3; #это то, что внутри абзаца
my $beforeP=$1.$2; #это то, что до абзаца
my $afterP=$4; #это — после абзаца
$last=$';
my $test_innerP = $innerP;
while($test_innerP =~ /(<(\w+).*?>(.*?)<\/\2>)/sig) { #вырезаем из содержимого абзаца контейнерные теги
$test_innerP = $`.'__'.$num++.'__'.$'; #заменяем их на метку вида
push @tokens, $1; #запоминаем какой метке принадлежит какой контейнер
}
$test_innerP =~ s/(мама)/<a href="http:\/\/dayte2.com">$1<\/a>/sgi; #спокойно заменяем вхождения мам
for(my $i=0; $i<=$#tokens; $i++) { #ставим токены на место
my $tok = $tokens[$i];
my $num = '__'.$i.'__';
$test_innerP =~ s/$num/$tok/;
}
$out_new.=$beforeP.$test_innerP.$afterP; #ставим абзац на место
}
return $out_new.$last; #важно не забыть остатки, которые в переменной $last
}
Вот и все. Таким не очень хитрым способом я решил задачку. Возможно, есть более элегантное решение, но я его не нашел. А это показало свою пригодность и жизнеспособность в реальных условиях.
Поясню на всякий случай значения переменных $` и $'. Первая обозначает то, что идет ДО вхождения регулярного выражения. Вторая — то, что идет после.