Регулярное выражение соответствует словам или фразам в строке, но НЕ совпадает с частью URL или внутри тегов . (РНР)

Я знаю, что регулярное выражение не идеально подходит для использования со строками HTML, и я смотрел PHP Simple HTML DOM Parser, но все же считаю, что это путь. Все теги HTML будут сгенерированы моим программным обеспечением форума, чтобы они были согласованными и допустимыми HTML.

То, что я пытаюсь сделать, это сделать плагин, который найдет список ключевых слов (или фраз) в строке HTML и заменит их ссылкой, которую я указываю. Например, если кто-то набирает:

I use Amazon for that.

он заменит его на:

I use <a href="#" onclick="location.href='http://www.amazon.com'; return false;">Amazon</a> for that.

Проблема заключается, конечно, в том, что если "амазонка" находится в URL-адресе, она также будет заменена. Я решил эту проблему с функцией обратного вызова, найденной на этом сайте, слегка измененной.

Но теперь у меня все еще есть проблема, она все равно заменяет слова между тегами открытия и закрытия.

<a href="#" onclick="location.href='http://www.amazon.com'; return false;">My Amazon Link</a>

Он будет соответствовать "Amazon" в "My Amazon Link"

Мне действительно нужно, чтобы регулярное выражение соответствовало "амазонке" где угодно, кроме <a href и </a>

Любые идеи?

Ответ 1

Использование DOM, безусловно, было бы предпочтительнее.

Однако вы можете уйти от этого:

$result = preg_replace('%Amazon(?![^<]*</a>)%i', '<a href="http://www.amazon.com">Amazon</a>', $subject);

Он соответствует Amazon, только если

  • за ним не следует закрывающий тег </a>,
  • он не является частью тега,
  • нет промежуточных тегов, т.е. е. он будет сброшен, если теги могут быть вложены внутри тегов <a>.

Поэтому он изменит это:

I use Amazon for that.
I use <a href="http://www.amazon.com">Amazon</a> for that.
<a href="http://www.amazon.com">My Amazon Link</a>
It will match the "Amazon" in "My Amazon Link"

в это:

I use <a href="http://www.amazon.com">Amazon</a> for that.
I use <a href="http://www.amazon.com">Amazon</a> for that.
<a href="http://www.amazon.com">My Amazon Link</a>
It will match the "<a href="http://www.amazon.com">Amazon</a>" in "My <a href="http://www.amazon.com">Amazon</a> Link"

Ответ 2

Не делай этого. Вы не можете надежно сделать это с помощью Regex, независимо от того, насколько совместим ваш HTML.

Что-то вроде этого должно работать, однако:

<?php
$dom = new DOMDocument;
$dom->load('test.xml');
$x = new DOMXPath($dom);

$nodes = $x->query("//text()[contains(., 'Amazon')][not(ancestor::a)]");

foreach ($nodes as $node) {
    while (false !== strpos($node->nodeValue, 'Amazon')) {
        $word = $node->splitText(strpos($node->nodeValue, 'Amazon'));
        $after = $word->splitText(6);

        $link = $dom->createElement('a');
        $link->setAttribute('href', 'http://www.amazon.com');

        $word->parentNode->replaceChild($link, $word);
        $link->appendChild($word);

        $node = $after;
    }
}

$html = $dom->saveHTML();
echo $html;

Это многословие, но оно действительно будет работать.

Ответ 3

Попробуйте здесь

Amazon(?![^<]*</a>)

Это приведет к поиску Amazon, и отрицательный lookahead гарантирует отсутствие закрывающего тега. И я ищу там только для не <, так что я не буду читать открывающий тег случайно.

http://regexr.com

Ответ 4

К сожалению, я думаю, что логика, в которой вы нуждаетесь, еще сложнее, чем сопоставление текстового шаблона: -/

Я знаю, что это не тот ответ, который вы хотите услышать, но вы, вероятно, получите лучшие результаты с помощью модели DOM.

Здесь обсуждение этого вопроса в другом месте: http://coderzone.org/forum/index.php?topic=84.0

Можно ли просто запустить фильтр один раз, так что вы не закончите с обманами? Или исходный корпус также может содержать ссылки?

Ответ 5

Джо, воскресив этот вопрос, потому что у него было простое решение, о котором не упоминалось. (Нашел ваш вопрос, проведя некоторое исследование для общего вопроса о как исключить шаблоны в regex.)

При всех отказах в использовании regex для синтаксического анализа html, это простой способ сделать это.

Здесь наше простое регулярное выражение:

<a.*?</a>(*SKIP)(*F)|amazon

Левая часть чередования соответствует полному тегу <a... </a>, а затем сознательно терпит неудачу. Правая сторона соответствует amazon, и мы знаем, что это правильный amazon, потому что это не соответствовало выражению слева.

В этой программе показано, как использовать регулярное выражение (см. результаты в нижней части онлайн-демонстрация):

<?php
$target = "word1 <a stuff amazon> </a> word2 amazon";
$regex = "~(?i)<a.*?</a>(*SKIP)(*F)|amazon~";
$repl= '<a href="http://www.amazon.com">Amazon</a>';
$new=preg_replace($regex,$repl,$target);
echo htmlentities($new);

Ссылка

Как сопоставить (или заменить) шаблон, за исключением ситуаций s1, s2, s3...

Ответ 6

Используйте этот код:

$p = '~((<a\s)(?(2)[^>]*?>))?(amazon)~smi';

$str = '<a href="http://www.amazon.com">Amazon</a>';

$s = preg_replace($p, "$1My $3 Link", $str);
var_dump($s);

OUTPUT

String(50) "<a href="http://www.amazon.com">My Amazon Link</a>"

Ответ 7

импровизация. Он должен связываться только в том случае, если это целое слово "Amazon", а не такие слова, как AmazonWorld.

$result = preg_replace('%\bAmazon(?![^<]*</a>)\b%i', '<a href="#" onclick="location.href='http://www.amazon.com'; return false;">Amazon</a>', $subject);