How do I replace asterix * with html <em> tags using regex

Viewed 157

I am trying to use regex to replace the * in a string with an <em> or </em> tag.

For example: My *name* is John outputs My <em>name</em> is John

However, if there are ** next to each other, I don't want to replace them with <em>.

I have the following code. The problem is that when I run it, it replaces the ** with <em> and </em>. I want

Hello *there* are two aster**es next to each other

to output

Hello <em>there</em> are two aster**es next to each other

Instead I get

Hello <em>there</em> are two aster<em></em>es next to each other

My code:

def emphasis(string):
 
    regex = re.compile('(\s?)\*(.*?)\*(\s?)')
    return re.sub(regex, replace_function, string)
    

def replace_function(input):
    match = input.group()
    if match:
        return re.sub('(\s?)\*(.*?)\*(\s?)', '\\1<em>\\2</em>\\3', match)

My test:

def test_if_two_asterix_are_next_to_each_other(self):
        title = "Hello *there* are two aster**es next to each other"
        expected = "Hello <em>there</em> are two aster**es next to each other"
        actual = _emphasis(title)
        self.assertEqual(actual,expected)

The test fails and instead I get:

Hello <em>there</em> are two aster<em></em>es es next to each other
2 Answers

The markdown library is perhaps the most appropriate solution here.

However, in terms of regex, the problem is that the starting and trailing delimiter is the same character. When you try to match one or more chars other than that character, you may catch the trailing * from the previous unsuccessful match and match through to the leading * of the next match.

Hence the easiest regex solution is to match two consecutive * chars and match *, any zero or more chars other than * and then a * in other contexts. Capture the contents between the two asterisks and wrap it with the tags you want inside a callable used as the replacement argument:

import re
pattern = r"\*{2,}|\*([^*]*)\*"
text = "Hello *there* are two aster**es next to each other"
print( re.sub(pattern, lambda x: f'<em>{x.group(1)}</em>' if x.group(1) else x.group(), text) )
## => Hello <em>there</em> are two aster**es next to each other

See the Python demo.

If I understand your question right, you're trying to convert markdown to html.

The easiest way to get your desired outcome is:

  1. In your terminal, run
$ pip install markdown
  1. In your Python program:
$ python3
Python 3.9.5
>>> import markdown
>>> title = "Hello *there* are two aster**es next to each other. *Right?*"
>>> expected = markdown.markdown(title)
>>> expected
'<p>Hello <em>there</em> are two aster**es next to each other</p>'

Sometimes it's hard building a regex so good that it covers all the corner cases. You found a corner case yourself (as described in your question: **). However, there are many other ones we both don't know even exist. There are people who have built tools for this already.

If you want to learn more about RegEx, however, there are a plethora of sources online.

Good luck!

P.S. You might notice that the markdown function wraps the output with <p></p> which might be undesirable. Hint: I don't think there's a way to suppress this automatically (see this). In a sense the solution for your problem brought with it another problem. However, arguably, removing <p> and </p> from an output is much easier than finding the right regex for your described question. Hint2: you can remove the first 3 and last 4 characters from your output with

expected = markdown.markdown(title)[3:-4:]
Related