6

Code:

str = '<br><br />A<br />B'
print(re.sub(r'<br.*?>\w$', '', str))

It is expected to return <br><br />A, but it returns an empty string ''!

Any suggestion?

AndersTornkvist
  • 2,610
  • 20
  • 38
Jet Guo
  • 243
  • 2
  • 6

2 Answers2

7

Greediness works from left to right, but not otherwise. It basically means "don't match unless you failed to match". Here's what's going on:

  1. The regex engine matches <br at the start of the string.
  2. .*? is ignored for now, it is lazy.
  3. Try to match >, and succeeds.
  4. Try to match \w and fails. Now it's interesting - the engine starts backtracking, and sees the .*? rule. In this case, . can match the first >, so there's still hope for that match.
  5. This keep happening until the regex reaches the slash. Then >\w can match, but $ fails. Again, the engine comes back to the lazy .* rule, and keeps matching, until it matches<br><br />A<br />B

Luckily, there's an easy solution: By replacing <br[^>]*>\w$ you don't allow matching outside of your tags, so it should replace the last occurrence.
Strictly speaking, this doesn't work well for HTML, because tag attributes can contain > characters, but I assume it's just an example.

Kobi
  • 135,331
  • 41
  • 252
  • 292
1

The non-greediness won't start later on like that. It matches the first <br and will non-greedily match the rest, which actually need to go to the end of the string because you specify the $.

To make it work the way you wanted, use

/<br[^<]*?>\w$/

but usually, it is not recommended to use regex to parse HTML, as some attribute's value can have < or > in it.

nonopolarity
  • 146,324
  • 131
  • 460
  • 740