From c95c3bf29192c030f7684c87c2577df479c9da59 Mon Sep 17 00:00:00 2001 From: santhreal <64453045+santhreal@users.noreply.github.com> Date: Fri, 17 Jul 2026 12:00:18 -0700 Subject: [PATCH] fix: avoid ValueError on Unicode-numeric start/colspan attributes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `
    ` and `` raised ValueError from the public markdownify() API. The guards used str.isnumeric()/str.isdigit(), which accept Unicode numerics (superscripts, fractions, CJK, roman, circled digits) that int() then rejects. Use str.isdecimal(), which accepts exactly what int() parses, so these values fall back to the default (start=1, colspan=1) as browsers do, matching the existing behavior for "foo"/"1.5". --- markdownify/__init__.py | 9 +++++---- tests/test_lists.py | 5 +++++ tests/test_tables.py | 13 +++++++++++++ 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/markdownify/__init__.py b/markdownify/__init__.py index 28cdaf6..01bb28a 100644 --- a/markdownify/__init__.py +++ b/markdownify/__init__.py @@ -634,7 +634,8 @@ def convert_li(self, el, text, parent_tags): # determine list item bullet character to use parent = el.parent if parent is not None and parent.name == 'ol': - if parent.get("start") and str(parent.get("start")).isnumeric(): + # isdecimal matches int(); isnumeric/isdigit accept chars int() rejects + if parent.get("start") and str(parent.get("start")).isdecimal(): start = int(parent.get("start")) else: start = 1 @@ -734,13 +735,13 @@ def convert_figcaption(self, el, text, parent_tags): def convert_td(self, el, text, parent_tags): colspan = 1 - if 'colspan' in el.attrs and el['colspan'].isdigit(): + if 'colspan' in el.attrs and el['colspan'].isdecimal(): colspan = max(1, min(1000, int(el['colspan']))) return ' ' + text.strip().replace("\n", " ") + ' |' * colspan def convert_th(self, el, text, parent_tags): colspan = 1 - if 'colspan' in el.attrs and el['colspan'].isdigit(): + if 'colspan' in el.attrs and el['colspan'].isdecimal(): colspan = max(1, min(1000, int(el['colspan']))) return ' ' + text.strip().replace("\n", " ") + ' |' * colspan @@ -761,7 +762,7 @@ def convert_tr(self, el, text, parent_tags): underline = '' full_colspan = 0 for cell in cells: - if 'colspan' in cell.attrs and cell['colspan'].isdigit(): + if 'colspan' in cell.attrs and cell['colspan'].isdecimal(): full_colspan += max(1, min(1000, int(cell['colspan']))) else: full_colspan += 1 diff --git a/tests/test_lists.py b/tests/test_lists.py index e9480ab..030222c 100644 --- a/tests/test_lists.py +++ b/tests/test_lists.py @@ -48,6 +48,11 @@ def test_ol(): assert md('
    1. a
    2. b
    ') == '\n\n1. a\n2. b\n' assert md('
    1. a
    2. b
    ') == '\n\n1. a\n2. b\n' assert md('
    1. a
    2. b
    ') == '\n\n1. a\n2. b\n' + # Unicode numerics (superscript/fraction/CJK/roman/circled) pass isnumeric() but int() rejects them; start falls back to 1 + assert md('
    1. a
    2. b
    ') == '\n\n1. a\n2. b\n' + assert md('
    1. a
    2. b
    ') == '\n\n1. a\n2. b\n' + assert md('
    1. a
    2. b
    ') == '\n\n1. a\n2. b\n' + assert md('
    1. a
    2. b
    ') == '\n\n1. a\n2. b\n' assert md('
    1. first para

      second para

    2. third para

      fourth para

    ') == '\n\n1234. first para\n\n second para\n1235. third para\n\n fourth para\n' diff --git a/tests/test_tables.py b/tests/test_tables.py index 7e0670c..bb07cd8 100644 --- a/tests/test_tables.py +++ b/tests/test_tables.py @@ -267,6 +267,18 @@ """ +# superscript ² and circled ② pass str.isdigit() but int() rejects them +table_with_unicode_colspan = """ + + + + + + + + +
    NameAge
    JillSmith
    """ + table_with_colspan_missing_head = """ @@ -300,6 +312,7 @@ def test_table(): assert md(table_with_caption) == 'TEXT\n\nCaption\n\n| | | |\n| --- | --- | --- |\n| Firstname | Lastname | Age |\n\n' assert md(table_with_colspan) == '\n\n| Name | | Age |\n| --- | --- | --- |\n| Jill | Smith | 50 |\n| Eve | Jackson | 94 |\n\n' assert md(table_with_undefined_colspan) == '\n\n| Name | Age |\n| --- | --- |\n| Jill | Smith |\n\n' + assert md(table_with_unicode_colspan) == '\n\n| Name | Age |\n| --- | --- |\n| Jill | Smith |\n\n' assert md(table_with_colspan_missing_head) == '\n\n| | | |\n| --- | --- | --- |\n| Name | | Age |\n| Jill | Smith | 50 |\n| Eve | Jackson | 94 |\n\n'
    Name