Repository: matthewwithanm/python-markdownify
Pull request #213 (2025-04-12 23:15:40)

make convert_hn() public instead of internal
Make `convert_hn()` public instead of internal, to allow for custom heading conversion functions.

AlexVonB: This reverts https://github.com/matthewwithanm/python-markdownify/commit/3466061ca9d33afd06532e2e79b418b8e478d66b which fixed the bug that converting `<hn>foo</hn>` crashed the converter. Do you see a way to fix both?
chrispy-snps: @AlexVonB - there is no `<hn>` tag in HTML so that was probably just an unfortunate coincidence with the typo.

We could rename `convert_hn()` to `convert_hn_all()` (which could still collide with `<hn_all>`). Or we could use a naming convention other than `convert_<name>()`, but I'm not sure I like that.

What are your thoughts?
AlexVonB: Perfect solution! I pondered a bit on the initial problem but this is the best solution

Patch:
diff --git a/markdownify/__init__.py b/markdownify/__init__.py
index 25196e6..780761c 100644
--- a/markdownify/__init__.py
+++ b/markdownify/__init__.py
@@ -363,11 +363,11 @@ class MarkdownConverter(object):
         if not self.should_convert_tag(tag_name):
             return None
 
-        # Handle headings with _convert_hn() function
+        # Handle headings with convert_hN() function
         match = re_html_heading.match(tag_name)
         if match:
             n = int(match.group(1))
-            return lambda el, text, parent_tags: self._convert_hn(n, el, text, parent_tags)
+            return lambda el, text, parent_tags: self.convert_hN(n, el, text, parent_tags)
 
         # For other tags, look up their conversion function by tag name
         convert_fn_name = "convert_%s" % re_make_convert_fn_name.sub('_', tag_name)
@@ -510,12 +510,12 @@ class MarkdownConverter(object):
 
         return '\n\n%s\n' % text
 
-    def _convert_hn(self, n, el, text, parent_tags):
-        """ Method name prefixed with _ to prevent <hn> to call this """
+    def convert_hN(self, n, el, text, parent_tags):
+        # convert_hN() converts <hN> tags, where N is any integer
         if '_inline' in parent_tags:
             return text
 
-        # prevent MemoryErrors in case of very large n
+        # Markdown does not support heading depths of n > 6
         n = max(1, min(6, n))
 
         style = self.options['heading_style'].lower()


Test patch:
diff --git a/tests/test_conversions.py b/tests/test_conversions.py
index 0df8f57..27951ee 100644
--- a/tests/test_conversions.py
+++ b/tests/test_conversions.py
@@ -164,7 +164,8 @@ def test_hn():
     assert md('<h5>Hello</h5>') == '\n\n##### Hello\n\n'
     assert md('<h6>Hello</h6>') == '\n\n###### Hello\n\n'
     assert md('<h10>Hello</h10>') == md('<h6>Hello</h6>')
-    assert md('<hn>Hello</hn>') == md('Hello')
+    assert md('<h0>Hello</h0>') == md('<h1>Hello</h1>')
+    assert md('<hx>Hello</hx>') == md('Hello')
 
 
 def test_hn_chained():
diff --git a/tests/test_custom_converter.py b/tests/test_custom_converter.py
index f4734c9..26faf90 100644
--- a/tests/test_custom_converter.py
+++ b/tests/test_custom_converter.py
@@ -12,7 +12,11 @@ class UnitTestConverter(MarkdownConverter):
 
     def convert_custom_tag(self, el, text, parent_tags):
         """Ensure conversion function is found for tags with special characters in name"""
-        return "FUNCTION USED: %s" % text
+        return "convert_custom_tag(): %s" % text
+
+    def convert_hN(self, n, el, text, parent_tags):
+        """Ensure conversion function is found for headings"""
+        return "convert_hN(%d): %s" % (n, text)
 
 
 def test_custom_conversion_functions():
@@ -23,7 +27,9 @@ def test_custom_conversion_functions():
     assert md('<img src="/path/to/img.jpg" alt="Alt text" title="Optional title" />text') == '![Alt text](/path/to/img.jpg "Optional title")\n\ntext'
     assert md('<img src="/path/to/img.jpg" alt="Alt text" />text') == '![Alt text](/path/to/img.jpg)\n\ntext'
 
-    assert md("<custom-tag>text</custom-tag>") == "FUNCTION USED: text"
+    assert md("<custom-tag>text</custom-tag>") == "convert_custom_tag(): text"
+
+    assert md("<h3>text</h3>") == "convert_hN(3): text"
 
 
 def test_soup():