Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
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
9 changes: 8 additions & 1 deletion docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,14 @@ and this project adheres to the
[Python Version Specification]: https://packaging.python.org/en/latest/specifications/version-specifiers/.
See the [Contributing Guide](contributing.md) for details.

## [unreleased]
## [Unreleased]

### Fixed

* Fix `codecs` deprecation.
* Fix issue with unclosed comment parsing in Python 3.14.
* Fix issue with unclosed declarations in Python 3.14.
* Fix issue with unclosed HTML tag `<foo` and Python 3.14.

## [3.8.1] - 2025-06-18

Expand Down
3 changes: 1 addition & 2 deletions markdown/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@

import sys
import optparse
import codecs
import warnings
import markdown
try:
Expand Down Expand Up @@ -100,7 +99,7 @@ def parse_options(args=None, values=None):

extension_configs = {}
if options.configfile:
with codecs.open(
with open(
options.configfile, mode="r", encoding=options.encoding
) as fp:
try:
Expand Down
2 changes: 1 addition & 1 deletion markdown/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,7 @@ def convertFile(
# Read the source
if input:
if isinstance(input, str):
input_file = codecs.open(input, mode="r", encoding=encoding)
input_file = open(input, mode="r", encoding=encoding)
else:
input_file = codecs.getreader(encoding)(input)
text = input_file.read()
Expand Down
5 changes: 4 additions & 1 deletion markdown/extensions/md_in_html.py
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,10 @@ def parse_html_declaration(self, i: int) -> int:
if self.rawdata[i:i+3] == '<![' and not self.rawdata[i:i+9] == '<![CDATA[':
# We have encountered the bug in #1534 (Python bug `gh-77057`).
# Provide an override until we drop support for Python < 3.13.
return self.parse_bogus_comment(i)
result = self.parse_bogus_comment(i)
if result == -1:
self.handle_data(self.rawdata[i:i + 1])
return i + 1
# The same override exists in `HTMLExtractor` without the check
# for `mdstack`. Therefore, use parent of `HTMLExtractor` instead.
return super(HTMLExtractor, self).parse_html_declaration(i)
Expand Down
24 changes: 22 additions & 2 deletions markdown/htmlparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ def __init__(self, md: Markdown, *args, **kwargs):

self.lineno_start_cache = [0]

self.override_comment_update = False

# This calls self.reset
super().__init__(*args, **kwargs)
self.md = md
Expand Down Expand Up @@ -253,8 +255,21 @@ def handle_entityref(self, name: str):
self.handle_empty_tag('&{};'.format(name), is_block=False)

def handle_comment(self, data: str):
# Check if the comment is unclosed, if so, we need to override position
i = self.line_offset + self.offset + len(data) + 4
if self.rawdata[i:i + 3] != '-->':
self.handle_data('<')
self.override_comment_update = True
return
self.handle_empty_tag('<!--{}-->'.format(data), is_block=True)

def updatepos(self, i: int, j: int) -> int:
if self.override_comment_update:
self.override_comment_update = False
i = 0
j = 1
return super().updatepos(i, j)

def handle_decl(self, data: str):
self.handle_empty_tag('<!{}>'.format(data), is_block=True)

Expand All @@ -278,7 +293,11 @@ def parse_html_declaration(self, i: int) -> int:
if self.rawdata[i:i+3] == '<![' and not self.rawdata[i:i+9] == '<![CDATA[':
# We have encountered the bug in #1534 (Python bug `gh-77057`).
# Provide an override until we drop support for Python < 3.13.
return self.parse_bogus_comment(i)
result = self.parse_bogus_comment(i)
if result == -1:
self.handle_data(self.rawdata[i:i + 1])
return i + 1
return result
return super().parse_html_declaration(i)
# This is not the beginning of a raw block so treat as plain data
# and avoid consuming any tags which may follow (see #1066).
Expand Down Expand Up @@ -313,7 +332,8 @@ def parse_starttag(self, i: int) -> int: # pragma: no cover
self.__starttag_text = None
endpos = self.check_for_whole_start_tag(i)
if endpos < 0:
return endpos
self.handle_data(self.rawdata[i:i + 1])
return i + 1
rawdata = self.rawdata
self.__starttag_text = rawdata[i:endpos]

Expand Down