snapshot of https://github.com/zulip/zulip.git @ 48c69911c7dd4e03b20552eb3bd77ad8151c1357 (2026-09-15)
Some checks failed
Code scanning / CodeQL (push) Failing after 15s
Zulip production suite / Ubuntu 22.04 production build (push) Failing after 26s
Zulip production suite / Debian 12 production install with custom db name and user (push) Has been skipped
Zulip production suite / Ubuntu 22.04 production install and PostgreSQL upgrade with pgroonga (push) Has been skipped
Zulip production suite / Ubuntu 24.04 production install (push) Has been skipped
Zulip production suite / Ubuntu 26.04 production install (push) Has been skipped
Zulip production suite / Debian 13 production install (push) Has been skipped
Zulip production suite / 7.0 Version Upgrade (push) Has been skipped
Zulip production suite / 8.0 Version Upgrade (push) Has been skipped
Zulip production suite / 6.0 Version Upgrade (push) Has been skipped
Zulip production suite / 10.0 Version Upgrade (push) Has been skipped
Zulip production suite / 9.0 Version Upgrade (push) Has been skipped
Zulip production suite / 12.0 Version Upgrade (push) Has been skipped
Zulip production suite / 11.0 Version Upgrade (push) Has been skipped
GitHub Actions Security Analysis with zizmor / zizmor (push) Failing after 3s
Zulip CI / Debian 12 (Python 3.11, backend + documentation) (push) Failing after 26s
Zulip CI / Ubuntu 22.04 (Python 3.10, backend + frontend) (push) Failing after 3s
Zulip CI / Ubuntu 24.04 (Python 3.12, backend) (push) Failing after 25s
Zulip CI / Ubuntu 26.04 (Python 3.14, backend) (push) Failing after 25s
Zulip CI / Debian 13 (Python 3.13, backend) (push) Failing after 28s
Zulip production suite / Required jobs (push) Has been cancelled
Zulip CI / Required jobs (push) Has been cancelled
API Documentation Update Check / check-feature-level-updated (push) Has been cancelled
API Documentation Update Check / notify-if-api-docs-changed (push) Has been cancelled
Some checks failed
Code scanning / CodeQL (push) Failing after 15s
Zulip production suite / Ubuntu 22.04 production build (push) Failing after 26s
Zulip production suite / Debian 12 production install with custom db name and user (push) Has been skipped
Zulip production suite / Ubuntu 22.04 production install and PostgreSQL upgrade with pgroonga (push) Has been skipped
Zulip production suite / Ubuntu 24.04 production install (push) Has been skipped
Zulip production suite / Ubuntu 26.04 production install (push) Has been skipped
Zulip production suite / Debian 13 production install (push) Has been skipped
Zulip production suite / 7.0 Version Upgrade (push) Has been skipped
Zulip production suite / 8.0 Version Upgrade (push) Has been skipped
Zulip production suite / 6.0 Version Upgrade (push) Has been skipped
Zulip production suite / 10.0 Version Upgrade (push) Has been skipped
Zulip production suite / 9.0 Version Upgrade (push) Has been skipped
Zulip production suite / 12.0 Version Upgrade (push) Has been skipped
Zulip production suite / 11.0 Version Upgrade (push) Has been skipped
GitHub Actions Security Analysis with zizmor / zizmor (push) Failing after 3s
Zulip CI / Debian 12 (Python 3.11, backend + documentation) (push) Failing after 26s
Zulip CI / Ubuntu 22.04 (Python 3.10, backend + frontend) (push) Failing after 3s
Zulip CI / Ubuntu 24.04 (Python 3.12, backend) (push) Failing after 25s
Zulip CI / Ubuntu 26.04 (Python 3.14, backend) (push) Failing after 25s
Zulip CI / Debian 13 (Python 3.13, backend) (push) Failing after 28s
Zulip production suite / Required jobs (push) Has been cancelled
Zulip CI / Required jobs (push) Has been cancelled
API Documentation Update Check / check-feature-level-updated (push) Has been cancelled
API Documentation Update Check / notify-if-api-docs-changed (push) Has been cancelled
This commit is contained in:
74
zerver/lib/markdown/from_html.py
Normal file
74
zerver/lib/markdown/from_html.py
Normal file
@@ -0,0 +1,74 @@
|
||||
import posixpath
|
||||
import re
|
||||
import sys
|
||||
import warnings
|
||||
from urllib.parse import urlsplit
|
||||
|
||||
import markdownify
|
||||
from bs4 import MarkupResemblesLocatorWarning, Tag
|
||||
|
||||
from zerver.lib.markdown import get_markdown_link_for_url, sanitize_url
|
||||
|
||||
|
||||
class ZulipMarkdownConverter(markdownify.MarkdownConverter):
|
||||
"""HTML-to-Markdown converter adapted to Zulip's Markdown.
|
||||
|
||||
Zulip doesn't inline-render external images, so images become
|
||||
`[label](src)` links instead of broken ``.
|
||||
"""
|
||||
|
||||
def convert_img(self, el: Tag, text: str, parent_tags: set[str]) -> str:
|
||||
src = el.get("src", "")
|
||||
alt = el.get("alt", "")
|
||||
# BeautifulSoup returns a list only for multi-valued attributes
|
||||
# (e.g. class), never for src or alt.
|
||||
assert isinstance(src, str)
|
||||
assert isinstance(alt, str)
|
||||
if not src:
|
||||
return alt
|
||||
url = sanitize_url(src)
|
||||
if not url:
|
||||
# Unlinkable src (e.g. data: URIs, unsafe schemes): emit just the
|
||||
# alt text rather than a bogus link.
|
||||
return alt
|
||||
label = alt or posixpath.basename(urlsplit(url).path)
|
||||
if not label:
|
||||
# No alt text, and no filename in the path to fall back on, as in
|
||||
# `<img src="https://example.com/?token=abc">`.
|
||||
return url
|
||||
if "a" in parent_tags:
|
||||
# Markdown has no nested links, so hand the label up for the
|
||||
# enclosing <a> to use as its text, with brackets stripped.
|
||||
return re.sub(r"\[|\]", "", label)
|
||||
return get_markdown_link_for_url(label, url)
|
||||
|
||||
def convert_title(self, el: Tag, text: str, parent_tags: set[str]) -> str:
|
||||
return ""
|
||||
|
||||
|
||||
def convert_html_to_markdown(
|
||||
html: str, *, converter_class: type[ZulipMarkdownConverter] = ZulipMarkdownConverter
|
||||
) -> str:
|
||||
# A bare URL is expected input here (the text/html part of an incoming
|
||||
# email), but BeautifulSoup warns about it ("looks like a URL, not markup").
|
||||
# Suppress the warning so that expected input doesn't spam our logs.
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore", MarkupResemblesLocatorWarning)
|
||||
converter = converter_class(
|
||||
# Zulip's Markdown supports only ATX headings, not Setext headings.
|
||||
heading_style="ATX",
|
||||
# Zulip's Markdown has no backslash escaping; it renders "\" as a
|
||||
# literal character.
|
||||
escape_asterisks=False,
|
||||
escape_underscores=False,
|
||||
# HTML treats a newline as insignificant whitespace, but Zulip
|
||||
# renders it as a line break. Collapse source newlines to spaces.
|
||||
# <br>, <pre>, and block-level elements still produce line breaks.
|
||||
wrap=True,
|
||||
# An effectively infinite width disables column wrapping. Don't
|
||||
# use None, which also disables wrapping but skips the pass that
|
||||
# strips indentation from lines following a <br>.
|
||||
wrap_width=sys.maxsize,
|
||||
)
|
||||
markdown: str = converter.convert(html).strip()
|
||||
return markdown
|
||||
Reference in New Issue
Block a user