From bcda6378769fcdc6a203a0a927bda70463e86404 Mon Sep 17 00:00:00 2001 From: Joseph Myers Date: Wed, 3 Apr 2024 20:27:48 +0000 Subject: [PATCH 1/2] Escape all characters with Markdown significance There are many punctuation characters that sometimes have significance in Markdown; more systematically escape them all (based on a new escape_misc configuration option). A limited attempt is made to limit the escaping of '.' and ')' to the context where they might have Markdown significance (after a number, where they can indicate an ordered list item); no such attempt is made for the other characters (and even that limiting of '.' and ')' may not be entirely safe in all cases, as it's possible the HTML could have the number outside the block being escaped in one go, e.g. `1.`. Fixes #99 --- README.rst | 5 +++++ markdownify/__init__.py | 5 +++++ tests/test_escaping.py | 23 +++++++++++++++++++++-- 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 51888ea..a0cd678 100644 --- a/README.rst +++ b/README.rst @@ -123,6 +123,11 @@ escape_underscores If set to ``False``, do not escape ``_`` to ``\_`` in text. Defaults to ``True``. +escape_misc + If set to ``False``, do not escape miscellaneous punctuation characters + that sometimes have Markdown significance in text. + Defaults to ``True``. + keep_inline_images_in Images are converted to their alt-text when the images are located inside headlines or table cells. If some inline images should be converted to diff --git a/markdownify/__init__.py b/markdownify/__init__.py index 86226d2..2f8c553 100644 --- a/markdownify/__init__.py +++ b/markdownify/__init__.py @@ -69,6 +69,7 @@ class DefaultOptions: default_title = False escape_asterisks = True escape_underscores = True + escape_misc = True heading_style = UNDERLINED keep_inline_images_in = [] newline_style = SPACES @@ -199,6 +200,10 @@ def should_convert_tag(self, tag): def escape(self, text): if not text: return '' + if self.options['escape_misc']: + for c in '\\&<`[>~#=+-|': + text = text.replace(c, '\\' + c) + text = re.sub(r'([0-9])([.)])', r'\1\\\2', text) if self.options['escape_asterisks']: text = text.replace('*', r'\*') if self.options['escape_underscores']: diff --git a/tests/test_escaping.py b/tests/test_escaping.py index 2f3a83e..eaef77d 100644 --- a/tests/test_escaping.py +++ b/tests/test_escaping.py @@ -12,7 +12,7 @@ def test_underscore(): def test_xml_entities(): - assert md('&') == '&' + assert md('&') == r'\&' def test_named_entities(): @@ -25,4 +25,23 @@ def test_hexadecimal_entities(): def test_single_escaping_entities(): - assert md('&amp;') == '&' + assert md('&amp;') == r'\&' + + +def text_misc(): + assert md('\\*') == r'\\\*' + assert md('') == r'\' + assert md('# foo') == r'\# foo' + assert md('> foo') == r'\> foo' + assert md('~~foo~~') == r'\~\~foo\~\~' + assert md('foo\n===\n') == 'foo\n\\=\\=\\=\n' + assert md('---\n') == '\\-\\-\\-\n' + assert md('+ x\n+ y\n') == '\\+ x\n\\+ y\n' + assert md('`x`') == r'\`x\`' + assert md('[text](link)') == r'\[text](link)' + assert md('1. x') == r'1\. x' + assert md('not a number. x') == r'not a number. x' + assert md('1) x') == r'1\) x' + assert md('not a number) x') == r'not a number) x' + assert md('|not table|') == r'\|not table\|' + assert md(r'\ &amp; | ` `', escape_misc=False) == r'\ & | ` `' From abee80626768cb679515c9c580de973f8b37ed0b Mon Sep 17 00:00:00 2001 From: AlexVonB Date: Thu, 4 Apr 2024 21:37:04 +0200 Subject: [PATCH 2/2] replace loop for single regex for substitution --- markdownify/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/markdownify/__init__.py b/markdownify/__init__.py index 2f8c553..1ae09a6 100644 --- a/markdownify/__init__.py +++ b/markdownify/__init__.py @@ -201,8 +201,7 @@ def escape(self, text): if not text: return '' if self.options['escape_misc']: - for c in '\\&<`[>~#=+-|': - text = text.replace(c, '\\' + c) + text = re.sub(r'([\\&<`[>~#=+|-])', r'\\\1', text) text = re.sub(r'([0-9])([.)])', r'\1\\\2', text) if self.options['escape_asterisks']: text = text.replace('*', r'\*')