Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ See the [Contributing Guide](contributing.md) for details.
### Fixed

* Fix an issue with excessive backtracking when matching inline code blocks (#1617).
* Fix quadratic rendering time when a paragraph contains many inline links (#1619).
Comment thread
waylan marked this conversation as resolved.
Outdated

## [3.10.3] - 2026-07-30

Expand Down
9 changes: 7 additions & 2 deletions markdown/treeprocessors.py

@waylan waylan Aug 11, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This changes the API for all inline patterns and processors. @facelessuser I think you have a better handle on this part of the code. Any input on that change here?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. I kept the search-index behavior change and documented it under Unreleased > Changed in b745bba, treating it as an inline-processor API/behavior change rather than a Fixed-only entry.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do have some mild concerns about this change. We are assuming that because the API changed here without breaking things, others using this API won't be broken.

When we changed the API here in the first time, we did so in a way that was non-breaking, one you could opt into. This was done by providing a different class that could be checked.

It is possible that we may need to employ some method ot indicate this is a different version of the new way. I really need to do some testing to understand the implications of the change. I will have to do some testing with this over in Pymdown Extensions, where we have a number of plugins using the new style, so I can get a better idea of what the impact here is.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that all the tests over on Pymdown Extensions passed with these changes, so that is a good sign.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@facelessuser did you intend to provide more input on this? Itis not clear to me if you had finished reviewing this.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I've just been busy. My initial pass indicated that I don't "think" it'll break anything.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can try to formally finish the review soon.

Original file line number Diff line number Diff line change
Expand Up @@ -323,12 +323,17 @@ def __applyPattern(
placeholder = self.__stashNode(node, pattern.type())

if new_style:
# Return the index just past the inserted placeholder so the
# next call scans only the unprocessed tail. Scanning from 0
# after every match makes repeated inline patterns quadratic.
return "{}{}{}".format(data[:start],
placeholder, data[end:]), True, 0
placeholder, data[end:]), True, start + len(placeholder)
else: # pragma: no cover
return "{}{}{}{}".format(leftData,
match.group(1),
placeholder, match.groups()[-1]), True, 0
placeholder, match.groups()[-1]), True, (
len(leftData) + len(match.group(1)) + len(placeholder)
)

def __build_ancestors(self, parent: etree.Element | None, parents: list[str]) -> None:
"""Build the ancestor list."""
Expand Down
20 changes: 20 additions & 0 deletions tests/test_syntax/inline/test_links.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@

from markdown.test_tools import TestCase

import time


class TestInlineLinks(TestCase):

Expand Down Expand Up @@ -434,3 +436,21 @@ def test_ref_round_brackets(self):
"""
)
)

def test_many_repeated_links(self):
# Regression test for #1619: rendering many inline links in one
# paragraph used to rescan the unprocessed text from the start after
# every match, making conversion quadratic in the number of links.
from markdown import Markdown

text = "[link](x)" * 8192
start = time.monotonic()
html = Markdown().convert(text)
elapsed = time.monotonic() - start

# The old implementation takes several seconds (or more on slow CI)
# for this input; the linear implementation finishes well under a
# second on any machine. Allow a generous ceiling to avoid flakes.
self.assertLess(elapsed, 5)
self.assertEqual(html.count("<a "), 8192)
self.assertEqual(html.count("</a>"), 8192)
Comment thread
waylan marked this conversation as resolved.
Outdated
Loading