Я наткнулся на действительно странную ошибку с функцией preg_replace PHP и некоторыми шаблонами регулярных выражений. То, что я пытаюсь сделать, это заменить пользовательские теги, ограниченные скобками, и преобразовать их в HTML. Регулярное выражение должно учитывать пользовательские теги «заливки», которые останутся с выведенным HTML, чтобы его можно было заменить «на лету», когда страница загружается (например, вместо имени сайта).
Каждый шаблон регулярного выражения будет работать сам по себе, но по некоторым причинам некоторые из них выйдут из функции раньше, если сначала будет отмечен один из других шаблонов. Когда я наткнулся на это, я использовал preg_match и цикл foreach, чтобы проверить шаблоны, прежде чем двигаться дальше, и вернул бы результат, если он найден, поэтому гипотетически это казалось бы свежим для каждого шаблона.
Это тоже не сработало.
Код проверки:
function replaceLTags($originalString){ $patterns = array( '#^\[l\]([^\s]+)\[/l\]$#i' => '<a href="$1">$1</a>', '#^\[l=([^\s]+)]([^\[]+)\[/l\]$#i'=> '<a href="$1">$2</a>', '#^\[l=([^\s]+) title=([^\[]+)]([^\[]+)\[/l\]$#i' => '<a href="$1" title="$2">$3</a>', '#^\[l=([^\s]+) rel=([^\[]+)]([^\[]+)\[/l\]$#i' => '<a href="$1" rel="$2">$3</a>', '#^\[l=([^\s]+) onClick=([^\[]+)]([^\[]+)\[/l\]$#i' => '<a href="$1" onClick="$2">$3</a>', '#^\[l=([^\s]+) style=([^\[]+)]([^\[]+)\[/l\]$#i' => '<a href="$1" style="$2">$3</a>', '#^\[l=([^\s]+) onClick=([^\[]+) style=([^\[]+)]([^\[]+)\[/l\]$#i' => '<a href="$1" onClick="$2" style="$3">$4</a>', '#^\[l=([^\s]+) class=([^\[]+) style=([^\[]+)]([^\[]+)\[/l\]$#i' => '<a href="$1" class="$2" style="$3">$4</a>', '#^\[l=([^\s]+) class=([^\[]+) rel=([^\[]+)] target=([^\[]+)]([^\[]+)\[/l\]$#i' => '<a href="$1" class="$2" rel="$3" target="$4">$5</a>' ); foreach ($patterns as $pattern => $replace){ if (preg_match($pattern, $originalString)){ return preg_replace($pattern, $replace, $originalString); } } } $string = '[l=[site_url]/site-category/ class=hello rel=nofollow target=_blank]Hello there[/l]'; echo $alteredString = $format->replaceLTags($string);
Вышеупомянутая «String» будет выглядеть так:
<a href="[site_url">/site-category/ class=hello rel=nofollow target=_blank]Hello there</a>
Когда это должно получиться следующим:
<a href="[site_url]/site-category/" class="hello" rel="nofollow" target="_blank">Hello there</a>
Но если переместить этот шаблон в списке, который будет проверен раньше, он будет правильно отформатировать.
Я в тупике, потому что кажется, что строка переписывается как-то каждый раз, когда она проверяется, хотя это и не имеет смысла.
Кажется, вы делаете гораздо больше работы, чем вам нужно. Вместо использования отдельного регулярного выражения / замены для каждого возможного списка атрибутов, почему бы не использовать preg_replace_callback
для обработки атрибутов на отдельном шаге? Например:
function replaceLTags($originalString){ return preg_replace_callback('#\[l=((?>[^\s\[\]]+|\[site_url\])+)([^\]]*)\](.*?)\[/l\]#', replaceWithinTags, $originalString); } function replaceWithinTags($groups){ return '<a href="' . $groups[1] . '"' . preg_replace('#(\s+\w+)=(\S+)#', '$1="$2"', $groups[2]) . '>' . $groups[3] . '</a>'; }
См. Полное демо здесь (обновлено, см. Комментарии).
Вот обновленная версия кода, основанная на новой информации, которая была представлена в комментариях:
function replaceLTags($originalString){ return preg_replace_callback('#\[l=((?>[^\s\[\]]+|\[\w+\])+)([^\]]*)\](.*?)\[/l\]#', replaceWithinTags, $originalString); } function replaceWithinTags($groups){ return '<a href="' . $groups[1] . '"' . preg_replace( '#(\s+[^\s=]+)\s*=\s*([^\s=]+(?>\s+[^\s=]+)*(?!\s*=))#', '$1="$2"', $groups[2]) . '>' . $groups[3] . '</a>'; }
демонстрация
В первом регулярном выражении я изменил [site_url]
на \[\w+\]
чтобы он соответствовал любому пользовательскому тегу заполнения.
Вот разбивка второго регулярного выражения:
(\s+[^\s=]+) # the attribute name and its leading whitespace \s*=\s* ( [^\s=]+ # the first word of the attribute value (?>\s+[^\s=]+)* # the second and subsequent words, if any (?!\s*=) # prevents the group above from consuming tag names )
Самая сложная часть – это сопоставление значений атрибутов нескольких слов. (?>\s+[^\s=]+)*
всегда будет использовать следующее имя тега, если оно есть, но lookahead заставляет его возвращаться назад. Обычно он отбрасывал только один символ за раз, но атомная группа эффективно заставляла его отступать целыми словами или вообще отсутствовать.
Вы перепутали регулярные выражения. Если вы печатаете строку на каждой итерации следующим образом:
foreach ($patterns as $pattern => $replace){ echo "String: $originalString\n"; if (preg_match($pattern, $originalString)){ return preg_replace($pattern, $replace, $originalString); } }
вы увидите, что строка не изменяется. Из моего прогона я заметил, что второе регулярное выражение совпадает. Я поместил третий параметр в вызов preg_match
и напечатал совпадения. Вот что я получил:
Array ( [0] => [l=[site_url]/site-category/ class=hello rel=nofollow target=_blank]Hello there[/l] [1] => [site_url [2] => /site-category/ class=hello rel=nofollow target=_blank]Hello there )
Причиной вашей непосредственной проблемы является двоякое:
Во-первых, существует опечатка в соответствующем регулярном выражении (последнее в массиве). Он имеет постороннюю буквальную прямоугольную скобку перед: " target="
. Другими словами, это:
'#^\[l=([^\s]+) class=([^\[]+) rel=([^\[]+)] target=([^\[]+)]([^\[]+)\[/l\]$#i'
Должен прочесть:
'#^\[l=([^\s]+) class=([^\[]+) rel=([^\[]+) target=([^\[]+)]([^\[]+)\[/l\]$#i'
Во-вторых, в массиве есть два регулярных выражения, которые соответствуют одной и той же строке, и, к сожалению, более конкретная из них (регулярное выражение выше того, что мы хотим) занимает второе место. Другим более общим регулярным выражением, которое соответствует, является вторым в массиве:
'#^\[l=([^\s]+)]([^\[]+)\[/l\]$#i'
Проблема в том, что последнее более общее регулярное выражение и удаление посторонней квадратной скобки решают проблему. Вот ваш первоначальный код, исправленный с использованием следующих двух изменений:
function replaceLTags($originalString){ $patterns = array( '#^\[l\]([^\s]+)\[/l\]$#i' => '<a href="$1">$1</a>', '#^\[l=([^\s]+) title=([^\[]+)]([^\[]+)\[/l\]$#i' => '<a href="$1" title="$2">$3</a>', '#^\[l=([^\s]+) rel=([^\[]+)]([^\[]+)\[/l\]$#i' => '<a href="$1" rel="$2">$3</a>', '#^\[l=([^\s]+) onClick=([^\[]+)]([^\[]+)\[/l\]$#i' => '<a href="$1" onClick="$2">$3</a>', '#^\[l=([^\s]+) style=([^\[]+)]([^\[]+)\[/l\]$#i' => '<a href="$1" style="$2">$3</a>', '#^\[l=([^\s]+) onClick=([^\[]+) style=([^\[]+)]([^\[]+)\[/l\]$#i' => '<a href="$1" onClick="$2" style="$3">$4</a>', '#^\[l=([^\s]+) class=([^\[]+) style=([^\[]+)]([^\[]+)\[/l\]$#i' => '<a href="$1" class="$2" style="$3">$4</a>', '#^\[l=([^\s]+) class=([^\[]+) rel=([^\[]+) target=([^\[]+)]([^\[]+)\[/l\]$#i' => '<a href="$1" class="$2" rel="$3" target="$4">$5</a>', '#^\[l=([^\s]+)]([^\[]+)\[/l\]$#i'=> '<a href="$1">$2</a>' ); foreach ($patterns as $pattern => $replace){ if (preg_match($pattern, $originalString)){ return preg_replace($pattern, $replace, $originalString); } } } $string = '[l=[site_url]/site-category/ class=hello rel=nofollow target=_blank]Hello there[/l]'; echo $alteredString = $format->replaceLTags($string);
Обратите внимание, что это только фиксирует непосредственную конкретную ошибку, описанную в вашем вопросе, и не затрагивает некоторые более фундаментальные проблемы с тем, что вы пытаетесь выполнить. Я представил несколько лучшее решение в качестве ответа на ваш следующий вопрос: как мне сделать REGEX ignore = в атрибуте тега? ,
Но, как говорили другие, смешивание двух разных языков разметки вместе, а обработка с помощью регулярного выражения вызывает проблемы.
Вот какой код общего назначения вы можете использовать, чтобы иметь меньше выражений, вы всегда можете удалить теги, которые не допускаются из последней строки.
<?php function replaceLTags($originalString) { if (preg_match('#^\[l\]([^\s]+)\[/l\]$#i', $originalString)) { // match a link with no description or tags return preg_replace('#^\[l\]([^\s]+)\[/l\]$#i', '<a href="$1">$1</a>', $originalString); } else if (preg_match('#^\[l=([^\s]+)\s*([^\]]*)\](.*?)\[/l\]#i', $originalString, $matches)) { // match a link with title and/or tags $attribs = $matches[2]; $attrStr = ''; if (preg_match_all('#([^=]+)=([^\s\]]+)#i', $attribs, $attribMatches) > 0) { $attrStr = ' '; for ($i = 0; $i < sizeof($attribMatches[0]); ++$i) { $attrStr .= $attribMatches[1][$i] . '="' . $attribMatches[2][$i] . '" '; } $attrStr = rtrim($attrStr); } return '<a href="' . $matches[1] . '"' . $attrStr . '>' . $matches[3] . '</a>'; } else { return $originalString; } } $strings = array( '[l]http://www.stackoverflow.com[/l]', '[l=[site_url]/site-category/ class=hello rel=nofollow target=_blank]Hello there[/l]', '[l=[site_url]/page.php?q=123]Link[/l]', '[l=http://www.stackoverflow.com/careers/ target=_blank class=default]Stack overflow[/l]' ); foreach($strings as $string) { $altered = replaceLTags($string); echo "{$altered}<br />\n"; }