diff --git a/docs/changelog.md b/docs/changelog.md index 94b4a2f5..6f27060e 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -16,6 +16,8 @@ See the [Contributing Guide](contributing.md) for details. ### Fixed +* Strip complete HTML tags with quoted `>` characters from `toc` labels and + generated heading IDs. * Fix quadratic-time regex backtracking in `attr_list` when a block ends with an unclosed attribute list followed by a long run of spaces, e.g. a line consisting only of `{` and some text with many trailing spaces (#1656). diff --git a/markdown/extensions/toc.py b/markdown/extensions/toc.py index 06812549..e0c4c74e 100644 --- a/markdown/extensions/toc.py +++ b/markdown/extensions/toc.py @@ -24,6 +24,7 @@ from ..util import parseBoolValue, AMP_SUBSTITUTE, deprecated, HTML_PLACEHOLDER_RE, AtomicString from ..treeprocessors import UnescapeTreeprocessor from ..serializers import RE_AMP +from ..inlinepatterns import HTML_RE import re import html import unicodedata @@ -108,8 +109,8 @@ def strip_tags(text: str) -> str: while (start := text.find('', start)) != -1: text = f'{text[:start]}{text[end + 3:]}' - while (start := text.find('<')) != -1 and (end := text.find('>', start)) != -1: - text = f'{text[:start]}{text[end + 1:]}' + # Use the inline HTML pattern to respect quoted attribute values. + text = re.sub(HTML_RE, '', text, flags=re.DOTALL) # Collapse whitespace text = ' '.join(text.split()) diff --git a/tests/test_syntax/extensions/test_toc.py b/tests/test_syntax/extensions/test_toc.py index 372ba09c..07df442b 100644 --- a/tests/test_syntax/extensions/test_toc.py +++ b/tests/test_syntax/extensions/test_toc.py @@ -155,6 +155,53 @@ def testTOCReset(self): self.assertEqual(md.toc, '') self.assertEqual(md.toc_tokens, []) + def testHTMLAttributeWithAngleBracket(self): + for quote in ['"', "'"]: + with self.subTest(quote=quote): + heading = f' 3{quote}>Python support' + self.assertMarkdownRenders( + f'# {heading}', + f'

{heading}

', + expected_attrs={ + 'toc': ( + '
\n\n
\n' + ), + 'toc_tokens': [{ + 'level': 1, + 'id': 'python-support', + 'name': 'Python support', + 'html': heading, + 'data-toc-label': '', + 'children': [] + }] + } + ) + + def testCustomLabelAttributeWithAngleBracket(self): + label = 'Python & support' + self.assertMarkdownRenders( + f"# Python support {{data-toc-label='{label}'}}", + '

Python support

', + expected_attrs={ + 'toc': ( + '
\n\n
\n' + ), + 'toc_tokens': [{ + 'level': 1, + 'id': 'python-support', + 'name': 'Python & support', + 'html': 'Python support', + 'data-toc-label': label, + 'children': [] + }] + }, + extensions=[TocExtension(), 'attr_list'] + ) + def testUniqueIds(self): self.assertMarkdownRenders( self.dedent( @@ -1454,6 +1501,18 @@ def testStripElementWithNewlines(self): 'foo bar' ) + def testStripElementWithQuotedBrackets(self): + for tag in [ + '', + "", + '', + '', + '', + "", + ]: + with self.subTest(tag=tag): + self.assertEqual(strip_tags(f'foo {tag}bar'), 'foo bar') + def testStripComment(self): self.assertEqual( strip_tags('foo bar'),