Skip to content

attr_list: preserve valueless attributes as provided - #1674

Merged
waylan merged 2 commits into
Python-Markdown:masterfrom
smoltafet:fix/attr-list-valueless-attributes-1501
Oct 26, 2026
Merged

waylan merged 2 commits into
Python-Markdown:masterfrom
smoltafet:fix/attr-list-valueless-attributes-1501

Conversation

@smoltafet

@smoltafet smoltafet commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Description

Fixes #1501.

Currently a valueless attribute in an attribute list is stored as key="key", which makes the same source render differently — with different meaning — depending on the output format:

>>> markdown.markdown('[download](file.txt){download}', extensions=['attr_list'])
'<p><a download="download" href="file.txt">download</a></p>'
>>> markdown.markdown('[download](file.txt){download}', extensions=['attr_list'], output_format='html')
'<p><a download href="file.txt">download</a></p>'

download="download" gives the downloaded file a default name of download; bare download does not. The reverse problem also existed: an explicit {download="download"} was collapsed to bare download under html.

This implements the approach sketched by @waylan in #1501 (comment):

  • A list of known boolean attributes is incorporated (markdown/serializers.py: BOOLEAN_ATTRIBUTES), differing per output format — the XHTML list is the one from the XHTML 1.0 spec, the HTML list is from the HTML spec — and download is on neither list.
  • At parse time (markdown/extensions/attr_list.py), a valueless attribute is represented internally as key=None. When the attribute is assigned to an element, the historic key="key" behaviour is maintained only for known boolean attributes of the active output format; any other valueless attribute keeps the value None, preserving what the user provided.
  • The serializer (markdown/serializers.py) renders a None value as the bare attribute name in both formats, and renders key="key" in the minimized form under html only for known boolean attributes — so explicit values are now preserved as provided for non-boolean attributes.
  • Knock-on fixes: UnescapeTreeprocessor skips None attribute values, and fenced_code's handle_attrs (which shares the parser) keeps its historic treatment of a valueless config word as a flag using the key as its own value.

Result:

>>> markdown.markdown('[download](file.txt){download}', extensions=['attr_list'])
'<p><a download href="file.txt">download</a></p>'
>>> markdown.markdown('[download](file.txt){download}', extensions=['attr_list'], output_format='html')
'<p><a download href="file.txt">download</a></p>'
>>> markdown.markdown('[download](file.txt){download="download"}', extensions=['attr_list'], output_format='html')
'<p><a download="download" href="file.txt">download</a></p>'
>>> markdown.markdown('[x](y){checked}', extensions=['attr_list'])
'<p><a checked="checked" href="y">x</a></p>'   # known boolean: historic behaviour

Backward compatibility: known boolean attributes render exactly as before in both formats. Three cases in the legacy attr_list fixture had expectations which changed, each being an instance of the bug (a valueless non-boolean attribute such as foo now renders as bare foo instead of foo="foo"); at the reviewer's request, those cases were moved out of the legacy fixture and into the new-style tests in tests/test_syntax/extensions/test_attr_list.py.

Tests: 17 new tests in tests/test_syntax/extensions/test_attr_list.py covering valueless non-boolean attributes in both formats, cross-format consistency, explicit download="download" preservation, known booleans (shared, XHTML-only compact, HTML-only open), the key=None parse representation, serializer-level rendering of None values, and the fenced-code config-word regression. Full suite (python -m unittest discover tests, the runner from tox.ini): 1154 tests OK (baseline 1137 OK, same 111 skips) — zero regressions. Note: this change was developed with AI assistance.

AI Assistance Disclosure

  • No AI tools were used in preparing this PR.

  • AI tools were used and I have disclosed which ones, and have fully reviewed and verified their output.

    AI tools were use to assist with the following:

    • Code
    • Code Comments
    • Documentation
    • Changelog Entry
    • Commit Message
    • The Description Above
    • Other: ______________

    Tool used: Muse, Anthropic's coding assistant. It generated the code changes, the new tests, the commit message, and the description above. I ran the full test suite locally to verify the results (1154 tests OK, zero regressions against the 1137-test baseline).

Checklist

@waylan

waylan commented Oct 24, 2026

Copy link
Copy Markdown
Member

As per our Contributing Guide:

All contributions must disclose whether AI tools were used or not. Disclosures of AI use must include the specific tools used and what content was generated by the tool, including, but not limited to code, documentation, comments, commit messages, and explanations. Issue and pull request templates must be followed when provided. If a template is ignored or deleted, it will be assumed that an AI agent was used to submit the pull request or issue autonomously and the pull request or issue may be closed immediately.

You appear to have deleted the PR Template. This will be your only warning.

@waylan waylan added the more-info-needed More information needs to be provided. label Oct 24, 2026
@smoltafet

Copy link
Copy Markdown
Contributor Author

Sorry about that — the description now follows the PR template, and the disclosure section is filled in with the tool used and what it generated.

@smoltafet

Copy link
Copy Markdown
Contributor Author

@waylan another look please ty

Comment thread tests/extensions/attr_list.html
@waylan waylan added requires-changes Awaiting updates after a review. and removed more-info-needed More information needs to be provided. labels Oct 26, 2026
…own#1501)

Remove the three legacy fixture cases whose expected output changed
from tests/extensions/attr_list.txt and attr_list.html and cover them
in tests/test_syntax/extensions/test_attr_list.py instead. Add a
changelog entry and reword two comments so the spelling check passes.
@smoltafet

Copy link
Copy Markdown
Contributor Author

@waylan fixed — moved the three changed legacy cases (random attributes, Bad Syntax header, key without value) out of tests/extensions/attr_list.txt/.html into new-style tests in tests/test_syntax/extensions/test_attr_list.py, and added the changelog entry. Also fixed the spelling check, it was flagging the word config in two comments I added. Tests pass locally (1154 OK) and the spelling check passes too.

@waylan waylan added approved The pull request is ready to be merged. and removed requires-changes Awaiting updates after a review. labels Oct 26, 2026
@waylan
waylan merged commit b97b56c into Python-Markdown:master Oct 26, 2026
14 checks passed
@smoltafet

Copy link
Copy Markdown
Contributor Author

@waylan thanks for the review and for merging this, appreciate it

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved The pull request is ready to be merged.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

attr_list: add option to preserve attributes without value as-is

2 participants