From 821dffc333f368021b1c3b1edbc8eee0f64c2a93 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 5 Aug 2026 11:51:57 +0300 Subject: [PATCH] gh-63866: Add the "htmlcharrefreplace" error handler Add html.htmlcharrefreplace_errors() which replaces an unencodable character with the corresponding HTML named character reference, or with a numeric character reference if there is no name for it. It is not registered as an error handler by default. Co-Authored-By: Claude Opus 5 (1M context) --- Doc/library/html.rst | 19 ++++++++++++++++ Lib/html/__init__.py | 21 ++++++++++++++++-- Lib/test/test_html.py | 22 +++++++++++++++++++ ...6-08-05-13-05-00.gh-issue-63866.htmlCR.rst | 4 ++++ 4 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-08-05-13-05-00.gh-issue-63866.htmlCR.rst diff --git a/Doc/library/html.rst b/Doc/library/html.rst index 65c49a4107a0487..0cfb5f8974a57b4 100644 --- a/Doc/library/html.rst +++ b/Doc/library/html.rst @@ -34,6 +34,25 @@ This module defines utilities to manipulate HTML. .. versionadded:: 3.4 + +.. function:: htmlcharrefreplace_errors(exception) + + Implements the ``htmlcharrefreplace`` error handling (for encoding only): + the unencodable character is replaced by the corresponding HTML named + character reference from :data:`html.entities.codepoint2name`, or by a + numeric character reference if there is no name for it. + + This error handler is not registered by default, you should register it + with :func:`codecs.register_error`:: + + >>> import codecs, html + >>> codecs.register_error('htmlcharrefreplace', + ... html.htmlcharrefreplace_errors) + >>> '∀ x∈ℜ'.encode('ascii', 'htmlcharrefreplace') + b'∀ x∈ℜ' + + .. versionadded:: next + -------------- Submodules in the ``html`` package are: diff --git a/Lib/html/__init__.py b/Lib/html/__init__.py index 1543460ca33b0ae..64bbaa44adbcf22 100644 --- a/Lib/html/__init__.py +++ b/Lib/html/__init__.py @@ -3,10 +3,10 @@ """ import re as _re -from html.entities import html5 as _html5 +from html.entities import codepoint2name as _codepoint2name, html5 as _html5 -__all__ = ['escape', 'unescape'] +__all__ = ['escape', 'unescape', 'htmlcharrefreplace_errors'] def escape(s, quote=True): @@ -130,3 +130,20 @@ def unescape(s): if '&' not in s: return s return _charref.sub(_replace_charref, s) + + +def htmlcharrefreplace_errors(exception): + """Implements the 'htmlcharrefreplace' error handling. + + Replaces an unencodable character with the corresponding HTML named + character reference, or with a numeric character reference if there + is no name for it. + """ + if not isinstance(exception, UnicodeEncodeError): + raise exception + replace = [] + for c in exception.object[exception.start:exception.end]: + n = ord(c) + name = _codepoint2name.get(n) + replace.append(f'&{name};' if name is not None else f'&#{n};') + return ''.join(replace), exception.end diff --git a/Lib/test/test_html.py b/Lib/test/test_html.py index 839e0a47a8499cc..4ca02f6fd61617e 100644 --- a/Lib/test/test_html.py +++ b/Lib/test/test_html.py @@ -2,6 +2,7 @@ Tests for the html module functions. """ +import codecs import html import unittest @@ -98,6 +99,27 @@ def check_num(num, expected): 'ÉricÉric&alphacentauriαcentauri') check('&co;', '&co;') + def test_htmlcharrefreplace_errors(self): + codecs.register_error('htmlcharrefreplace', + html.htmlcharrefreplace_errors) + self.assertEqual('\u2200 x\u2208\u211c'.encode('ascii', + 'htmlcharrefreplace'), + b'∀ x∈ℜ') + # Characters without a name are replaced with a numeric reference. + self.assertEqual('[$\xa5\u20a3\u20ac\U0001d56b]'.encode( + 'latin1', 'htmlcharrefreplace'), + b'[$\xa5₣€𝕫]') + # Surrogates have no name either. + self.assertEqual('\udcff'.encode('ascii', 'htmlcharrefreplace'), + b'�') + + def test_htmlcharrefreplace_errors_bad_exception(self): + with self.assertRaises(UnicodeDecodeError): + html.htmlcharrefreplace_errors( + UnicodeDecodeError('ascii', b'\xff', 0, 1, 'ordinal')) + with self.assertRaises(TypeError): + html.htmlcharrefreplace_errors(TypeError('spam')) + if __name__ == '__main__': unittest.main() diff --git a/Misc/NEWS.d/next/Library/2026-08-05-13-05-00.gh-issue-63866.htmlCR.rst b/Misc/NEWS.d/next/Library/2026-08-05-13-05-00.gh-issue-63866.htmlCR.rst new file mode 100644 index 000000000000000..6a3e2047f56fc10 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-08-05-13-05-00.gh-issue-63866.htmlCR.rst @@ -0,0 +1,4 @@ +Add :func:`html.htmlcharrefreplace_errors` which implements the +``htmlcharrefreplace`` error handler: an unencodable character is replaced +with the corresponding HTML named or numeric character reference. It is not +registered by default.