diff --git a/markdownify/__init__.py b/markdownify/__init__.py index 28cdaf6..99b3c63 100644 --- a/markdownify/__init__.py +++ b/markdownify/__init__.py @@ -702,7 +702,13 @@ def convert_pre(self, el, text, parent_tags): else: raise ValueError('Invalid value for strip_pre: %s' % self.options['strip_pre']) - return '\n\n```%s\n%s\n```\n\n' % (code_language, text) + # Use a fence long enough that no backtick run inside the code can close + # it early (CommonMark section 4.5: the closing fence must have at least + # as many backticks as the opening). + max_backticks = max((len(match) for match in re.findall(re_backtick_runs, text)), default=0) + fence = '`' * max(3, max_backticks + 1) + + return '\n\n%s%s\n%s\n%s\n\n' % (fence, code_language, text, fence) def convert_q(self, el, text, parent_tags): return '"' + text + '"' diff --git a/tests/test_conversions.py b/tests/test_conversions.py index c95483c..0d74845 100644 --- a/tests/test_conversions.py +++ b/tests/test_conversions.py @@ -311,6 +311,15 @@ def test_pre(): assert md("
foo
\nbar\nbaz", sub_symbol="^") == "\n\nfoo\n\n```\nbar\n```\n\nbaz" +def test_pre_backticks(): + # A backtick run inside the code must not close the fence early: the fence + # needs at least one more backtick than the longest run in the content. + assert md('
foo\n```\nbar') == '\n\n````\nfoo\n```\nbar\n````\n\n' + assert md('
```') == '\n\n````\n```\n````\n\n'
+ assert md('a````b') == '\n\n`````\na````b\n`````\n\n' + assert md('
plain') == '\n\n```\nplain\n```\n\n' + + def test_q(): assert md('foo
quotebar') == 'foo "quote" bar' assert md('foo
quotebar') == 'foo "quote" bar'