From b0eb0805ac0eb70537db36e00489089dc335e618 Mon Sep 17 00:00:00 2001 From: Guillermo Date: Mon, 15 Jun 2026 12:34:51 +0200 Subject: [PATCH] fix: recover from malformed numeric entities --- CHANGELOG.md | 1 + slugify/slugify.py | 24 ++++++++++++++++-------- test.py | 20 ++++++++++++++++++++ 3 files changed, 37 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 537460e..608f34c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## Unreleased +- Decode valid numeric entities even when malformed numeric entities are present. - Support Python 3.14. - Drop support for Python 3.9 and lower. - Use tox for local test runs and in CI. diff --git a/slugify/slugify.py b/slugify/slugify.py index 9b5f27f..b7d8a6e 100644 --- a/slugify/slugify.py +++ b/slugify/slugify.py @@ -24,6 +24,20 @@ DEFAULT_SEPARATOR = '-' +def _char_from_decimal_codepoint(match: re.Match[str]) -> str: + try: + return chr(int(match.group(1))) + except (OverflowError, ValueError): + return match.group(0) + + +def _char_from_hex_codepoint(match: re.Match[str]) -> str: + try: + return chr(int(match.group(1), 16)) + except (OverflowError, ValueError): + return match.group(0) + + def smart_truncate( string: str, max_length: int = 0, @@ -134,17 +148,11 @@ def slugify( # decimal character reference if decimal: - try: - text = DECIMAL_PATTERN.sub(lambda m: chr(int(m.group(1))), text) - except Exception: - pass + text = DECIMAL_PATTERN.sub(_char_from_decimal_codepoint, text) # hexadecimal character reference if hexadecimal: - try: - text = HEX_PATTERN.sub(lambda m: chr(int(m.group(1), 16)), text) - except Exception: - pass + text = HEX_PATTERN.sub(_char_from_hex_codepoint, text) # re normalize text if allow_unicode: diff --git a/test.py b/test.py index fcec4b6..b8aac57 100644 --- a/test.py +++ b/test.py @@ -166,6 +166,11 @@ def test_html_decimal_on(self): r = slugify(txt, decimal=True) self.assertEqual(r, 'z') + def test_html_decimal_mixed_valid_invalid(self): + txt = 'Ž �' + r = slugify(txt, decimal=True) + self.assertEqual(r, 'z-99999999') + def test_html_decimal_off(self): txt = 'Ž' r = slugify(txt, entities=False, decimal=False) @@ -176,6 +181,11 @@ def test_html_hexadecimal_on(self): r = slugify(txt, hexadecimal=True) self.assertEqual(r, 'z') + def test_html_hexadecimal_mixed_valid_invalid(self): + txt = 'Ž �' + r = slugify(txt, hexadecimal=True) + self.assertEqual(r, 'z-x110000') + def test_html_hexadecimal_off(self): txt = 'Ž' r = slugify(txt, hexadecimal=False) @@ -418,6 +428,11 @@ def test_html_decimal_on(self): r = slugify(txt, allow_unicode=True, decimal=True) self.assertEqual(r, 'ž') + def test_html_decimal_mixed_valid_invalid(self): + txt = 'Ž �' + r = slugify(txt, allow_unicode=True, decimal=True) + self.assertEqual(r, 'ž-99999999') + def test_html_decimal_off(self): txt = 'Ž' r = slugify(txt, allow_unicode=True, entities=False, decimal=False) @@ -428,6 +443,11 @@ def test_html_hexadecimal_on(self): r = slugify(txt, allow_unicode=True, hexadecimal=True) self.assertEqual(r, 'ž') + def test_html_hexadecimal_mixed_valid_invalid(self): + txt = 'Ž �' + r = slugify(txt, allow_unicode=True, hexadecimal=True) + self.assertEqual(r, 'ž-x110000') + def test_html_hexadecimal_off(self): txt = 'Ž' r = slugify(txt, allow_unicode=True, hexadecimal=False)