Skip to content

Commit a85013d

Browse files
fix(links): support mailto: and tel: links in HTML and DOCX conversion (#2644)
1 parent 4cc9fa1 commit a85013d

2 files changed

Lines changed: 26 additions & 8 deletions

File tree

‎packages/markitdown/src/markitdown/converters/_markdownify.py‎

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -71,19 +71,27 @@ def convert_a(
7171
href = el.get("href")
7272
title = el.get("title")
7373

74-
# Escape URIs and skip non-http or file schemes
74+
# Escape URIs and skip non-http or file schemes.
75+
# mailto: and tel: links are kept because they are common in
76+
# documents and do not execute code.
77+
_SAFE_SCHEMES = {"http", "https", "file", "mailto", "tel"}
7578
if href:
7679
try:
7780
parsed_url = urlparse(href) # type: ignore
78-
if parsed_url.scheme and parsed_url.scheme.lower() not in ["http", "https", "file"]: # type: ignore
81+
if parsed_url.scheme and parsed_url.scheme.lower() not in _SAFE_SCHEMES: # type: ignore
7982
return "%s%s%s" % (prefix, text, suffix)
80-
href = urlunparse(
81-
parsed_url._replace(
82-
path=_quote_path_preserving_percent_encoded_octets(
83-
parsed_url.path
83+
# mailto: and tel: URIs have no hierarchical path to
84+
# percent-encode, so pass them through unchanged.
85+
if parsed_url.scheme and parsed_url.scheme.lower() in {"mailto", "tel"}:
86+
pass # keep href as-is
87+
else:
88+
href = urlunparse(
89+
parsed_url._replace(
90+
path=_quote_path_preserving_percent_encoded_octets(
91+
parsed_url.path
92+
)
8493
)
85-
)
86-
) # type: ignore
94+
) # type: ignore
8795
except ValueError: # It's not clear if this ever gets thrown
8896
return "%s%s%s" % (prefix, text, suffix)
8997

‎packages/markitdown/tests/test_html.py‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,16 @@ def test_html_href_does_not_quote_query_or_fragment() -> None:
101101
assert f"[example]({expected_href})" in markdown
102102

103103

104+
def test_html_href_preserves_mailto_and_tel_links() -> None:
105+
html = (
106+
'<p>Contact <a href="mailto:team@example.com">the team</a> or '
107+
'<a href="tel:+1234567890">call us</a>.</p>'
108+
)
109+
markdown = _convert_html(html)
110+
assert "[the team](mailto:team@example.com)" in markdown
111+
assert "[call us](tel:+1234567890)" in markdown
112+
113+
104114
def test_img_prefers_data_src_over_placeholder_data_uri() -> None:
105115
placeholder = (
106116
"data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBTAA7"

0 commit comments

Comments
 (0)