From 8b1950054ebae89ece3b8e46b1ef7f48df064b58 Mon Sep 17 00:00:00 2001 From: Naaz nuria Date: Thu, 29 Oct 2026 15:46:47 +0530 Subject: [PATCH] Ignore pygments_formatter in fenced_code attribute lists `FencedBlockPreprocessor.run` merges every key/value pair from a fenced block's attribute list into the `codehilite` settings, so a block could set `pygments_formatter` and replace the formatter chosen in the `codehilite` configuration. Pygments' `text` formatter writes the code out as is, so `{ .text pygments_formatter='text' }` rendered the block's content as markup instead of escaping it like any other code block. Other formatters do not produce HTML at all. The key is now dropped before the merge, in the Pygments branch only. The fenced code docs only list `HtmlFormatter` and lexer options as settable per block, and `CodeHilite` already forces the `full` option off for a similar reason. The non-Pygments branch and the other per-block options are unchanged. --- docs/changelog.md | 3 +++ markdown/extensions/fenced_code.py | 3 +++ .../extensions/test_fenced_code.py | 26 +++++++++++++++++++ 3 files changed, 32 insertions(+) diff --git a/docs/changelog.md b/docs/changelog.md index cba971a41..3eb295b47 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -35,6 +35,9 @@ See the [Contributing Guide](contributing.md) for details. and closing backticks did not match (#1668). * Escape the language name in the `codehilite` extension's non-Pygments output to maintain consistency with Pygments output. +* Ignore a `pygments_formatter` key in a fenced code block's attribute list so + the formatter is always the one set in the `codehilite` configuration. A + formatter such as `text` would otherwise leave the code unescaped. * Preserve attributes without a value in the `attr_list` extension exactly as provided, rather than rendering them as `key="key"`, so the same source renders consistently under both the `html` and `xhtml` output formats diff --git a/markdown/extensions/fenced_code.py b/markdown/extensions/fenced_code.py index 6e2ed6b18..e3519a9ca 100644 --- a/markdown/extensions/fenced_code.py +++ b/markdown/extensions/fenced_code.py @@ -126,6 +126,9 @@ def run(self, lines: list[str]) -> list[str]: # is enabled, so we call it to highlight the code if self.codehilite_conf and self.codehilite_conf['use_pygments'] and config.get('use_pygments', True): local_config = self.codehilite_conf.copy() + # Only the `codehilite` configuration may set the formatter, as some + # formatters (such as `text`) do not escape the code. + config.pop('pygments_formatter', None) local_config.update(config) # Combine classes with `cssclass`. Ensure `cssclass` is at end # as Pygments appends a suffix under certain circumstances. diff --git a/tests/test_syntax/extensions/test_fenced_code.py b/tests/test_syntax/extensions/test_fenced_code.py index cbe101db3..f6b3a56bc 100644 --- a/tests/test_syntax/extensions/test_fenced_code.py +++ b/tests/test_syntax/extensions/test_fenced_code.py @@ -1079,3 +1079,29 @@ def testInvalidCustomPygmentsFormatter(self): 'fenced_code' ] ) + + def testFencedLanguageAttrPygmentsFormatterIgnored(self): + # The formatter can only be set in the `codehilite` configuration. + if has_pygments: + expected = self.dedent( + ''' +
<b>Some HTML</b>
+                
+ ''' + ) + else: + expected = ( + '
<b>Some HTML</b>\n'
+                '
' + ) + self.assertMarkdownRenders( + self.dedent( + ''' + ``` { .text pygments_formatter='text' } + Some HTML + ``` + ''' + ), + expected, + extensions=['codehilite', 'fenced_code'] + )