diff --git a/CHANGES.md b/CHANGES.md index 10aabd33..4fd21de2 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,7 @@ ## python-markdown2 2.5.6 (not yet released) +- [pull #775] Speed up documents with many indented code blocks by skipping the `` lookahead when the text has no `` - [pull #762] Speed up documents with many links by skipping the backslash-escape scan for text without a backslash - [pull #748] Speed up `_unescape_special_chars` by skipping text with no hashes and dropping the full-text comparison per pass (#635) - [pull #749] URL-encode destinations generated by the `link-patterns` extra (#55) diff --git a/lib/markdown2.py b/lib/markdown2.py index d6285404..a829e23a 100755 --- a/lib/markdown2.py +++ b/lib/markdown2.py @@ -2088,7 +2088,7 @@ def _html_class_str_from_tag(self, tag: str) -> str: @mark_stage(Stage.CODE_BLOCKS) def _do_code_blocks(self, text: str) -> str: """Process Markdown `
` blocks."""
- code_block_re = re.compile(r'''
+ code_block_pat = r'''
(?:\n\n|\A\n?)
( # $1 = the code block -- one or more lines, starting with a space/tab
(?:
@@ -2097,11 +2097,13 @@ def _do_code_blocks(self, text: str) -> str:
)+
)
((?=^[ ]{0,%d}\S)|\Z) # Lookahead for non-space at line-start, or end of doc
+ ''' % (self.tab_width, self.tab_width)
+ if '' in text:
# Lookahead to make sure this block isn't already in a code block.
- # Needed when syntax highlighting is being used.
- (?!([^<]|<(/?)span)*\)
- ''' % (self.tab_width, self.tab_width),
- re.M | re.X)
+ # Needed when syntax highlighting is being used. It scans ahead
+ # after every block, so leave it out when it can't match.
+ code_block_pat += r'(?!([^<]|<(/?)span)*\)'
+ code_block_re = re.compile(code_block_pat, re.M | re.X)
return code_block_re.sub(self._code_block_sub, text)
# Rules for a code span:
diff --git a/test/test_redos.py b/test/test_redos.py
index 1866a391..1759dddb 100644
--- a/test/test_redos.py
+++ b/test/test_redos.py
@@ -66,6 +66,11 @@ def issue_metadata_list_redos():
return "---\nitems:\n - " + " " * 32000 + "\n---\n\n# hello\n"
+def many_code_blocks():
+ # not technically a redos: each code block used to rescan the rest of the text
+ return "".join(f"Paragraph {i}:\n\n code {i}\n\n" for i in range(3000))
+
+
# whack everything in a dict for easy lookup later on
CASES = {
fn.__name__: (fn, extras)
@@ -82,6 +87,7 @@ def issue_metadata_list_redos():
(issue_707_spaced_attrs, None),
(issue_metadata_redos, ['metadata']),
(issue_metadata_list_redos, ['metadata']),
+ (many_code_blocks, None),
]
}