Here, we might be able to solve this problem.
Let's start with a not followed by a word strategy to exclude our undesired <br>
, to see if that would work. For that, we just need to close our expression with an end char and we might want to not bound it with start char:
((<b>([a-z]+)<\/b>)((?!<br>).)*)$
We have also added extra capturing groups ()
, which we can remove it, if we don't wish to have it.

Test
$re = '/((<b>([a-z]+)<\/b>)((?!<br>).)*)$/im';
$str = '<b>word</b><br>
<b>word</b> <br>
<b>word</b> in text
half<b>word</b> ';
preg_match_all($re, $str, $matches, PREG_SET_ORDER, 0);
var_dump($matches);
Output
array(2) {
[0]=>
array(5) {
[0]=>
string(19) "<b>word</b> in text"
[1]=>
string(19) "<b>word</b> in text"
[2]=>
string(11) "<b>word</b>"
[3]=>
string(4) "word"
[4]=>
string(1) "t"
}
[1]=>
array(5) {
[0]=>
string(12) "<b>word</b> "
[1]=>
string(12) "<b>word</b> "
[2]=>
string(11) "<b>word</b>"
[3]=>
string(4) "word"
[4]=>
string(1) " "
}
}
Demo
const regex = /((<b>([a-z]+)<\/b>)((?!<br>).)*)$/igm;
const str = `<b>word</b><br>
<b>word</b> <br>
<b>word</b> in text
half<b>word</b> `;
let m;
while ((m = regex.exec(str)) !== null) {
// This is necessary to avoid infinite loops with zero-width matches
if (m.index === regex.lastIndex) {
regex.lastIndex++;
}
// The result can be accessed through the `m`-variable.
m.forEach((match, groupIndex) => {
console.log(`Found match, group ${groupIndex}: ${match}`);
});
}