Лично я бы сначала использовал что-то вроде preg_split:
$string = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. <h1>Lorem ipsum dolor sit</h1> Proin libero erat, malesuada eget volutpat vitae, efficitur vitae ipsum. Vivamus et <h2>Lorem ipsum dolor sit</h2> justo non quam laoreet euismod. Ut eget dapibus ligula. <img src="url" alt="Lorem ipsum dolor sit"/> Vestibulum vestibulum.';
$split = preg_split('/(<[^\/]+(?:\/|<\/[^>]+)>)/', $string, null, PREG_SPLIT_DELIM_CAPTURE);
Что дает вам это (это основная вещь, которую нам нужно сделать):
Array
(
[0] => Lorem ipsum dolor sit amet, consectetur adipiscing elit.
[1] => <h1>Lorem ipsum dolor sit</h1>
[2] => Proin libero erat, malesuada eget volutpat vitae, efficitur vitae ipsum. Vivamus et
[3] => <h2>Lorem ipsum dolor sit</h2>
[4] => justo non quam laoreet euismod. Ut eget dapibus ligula.
[5] => <img src="url" alt="Lorem ipsum dolor sit"/>
[6] => Vestibulum vestibulum.
)
Теперь мы разделили эти элементы внутри тегов. Так что теперь мы можем перебрать этот набор и проверить, является ли начальный символ <
или нет, и иметь представление, находится ли он внутри или снаружи тега. Это должно работать до тех пор, пока ваши теги оканчиваются на </...>
или />
.
В основном HTML-теги + контент становятся разделителями, которые мы также фиксируем.
Дело в том, что обычный Regex не способен анализировать HTML, поскольку он не является обычным языком. Таким образом, мы должны сделать некоторую работу в PHP, чтобы связать все это вместе. Мы можем разбить его и упростить проблему с помощью простого регулярного выражения, как я сделал здесь.
$subject = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. <h1>Lorem ipsum dolor sit</h1> Proin libero erat, malesuada eget volutpat vitae, efficitur vitae ipsum. Vivamus et <h2>Lorem ipsum dolor sit</h2> Lorem justo non quam laoreet euismod. Ut eget dapibus ligula. <img src="url" alt="Lorem ipsum dolor sit"/> Vestibulum vestibulum.';
//word to replace
$search = 'Lorem';
//stuff to replace with
$replace = '<a href="Lorem">foo</a>';
//what match to replace
$occurrence = 2;
function str_ireplace_n($search, $replace, $subject, $occurrence){
$search = preg_quote($search);
//separate the HTML from the "body" text
$split = preg_split('/(<(?:h1|h2|h3|img)[^\/]+(?:\/|<\/[^>]+)>)/', $subject, null, PREG_SPLIT_DELIM_CAPTURE);
//the number of current matches
$match = 0;
foreach($split as &$s){
//if strpos < is 0 it's the first character - meaning its part of HTML (we don't want that)
//if it matches search
if(0 !== strpos($s,'<') && preg_match('/\b'.$search.'\b/i', $s)){
//increment the match counter
++$match;
//replace the match if it's the nth one
if($match == $occurrence) $s = preg_replace('/\b'.$search.'\b/i',$replace,$s);
}
}
return implode($split);
}
echo str_ireplace_n($search, $replace, $subject, $occurrence);
Выход:
Lorem ipsum dolor sit amet, consectetur adipiscing elit. <h1>Lorem ipsum dolor sit</h1>
Proin libero erat, malesuada eget volutpat vitae, efficitur vitae ipsum. Vivamus et
<h2>Lorem ipsum dolor sit</h2> <a href="Lorem">foo</a> justo non quam laoreet euismod.
Ut eget dapibus ligula. <img src="url" alt="Lorem ipsum dolor sit"/> Vestibulum vestibulum.
Это заменяемая часть <a href="Lorem">foo</a>
Я добавил несколько строковых возвратов для удобства чтения (в выходных данных) и еще один «Lorem» (во входных данных), так как за пределами HTML-тегов не было ни одного второго совпадения. В любом случае, если вы заметили, ничего внутри тегов HTML не было изменено. И в этом случае был изменен только второй матч.
Не совсем на 100% ясно, что вам нужно (как это часто бывает с вопросами такого типа), поэтому я пытаюсь объяснить, как это сделать, а не просто делать это.
Песочница