Skip to content

Commit b857d00

Browse files
committed
svg handling
1 parent a5e69c3 commit b857d00

3 files changed

Lines changed: 86 additions & 13 deletions

File tree

src/md2bbcode/image_rewrite.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
from typing import Optional
2+
from urllib.parse import parse_qs, quote, urlencode, urlparse
3+
4+
_RASTER_SHIELDS_BASE = "https://raster.shields.io"
5+
_WESERV_BASE = "https://images.weserv.nl/"
6+
7+
8+
def rewrite_svg_url(url: str) -> Optional[str]:
9+
if not url:
10+
return url
11+
12+
parsed = urlparse(url)
13+
if _is_github_actions_badge(parsed):
14+
return _rewrite_github_actions_badge(parsed)
15+
16+
if _should_rasterize(parsed):
17+
if parsed.scheme not in ("http", "https"):
18+
return None
19+
return _wrap_weserv(url)
20+
21+
return url
22+
23+
24+
def _is_github_actions_badge(parsed) -> bool:
25+
if parsed.scheme not in ("http", "https"):
26+
return False
27+
if parsed.netloc.lower() != "github.com":
28+
return False
29+
30+
parts = parsed.path.strip("/").split("/")
31+
return (
32+
len(parts) >= 6
33+
and parts[2] == "actions"
34+
and parts[3] == "workflows"
35+
and parts[5].lower() == "badge.svg"
36+
)
37+
38+
39+
def _rewrite_github_actions_badge(parsed) -> str:
40+
parts = parsed.path.strip("/").split("/")
41+
owner = parts[0]
42+
repo = parts[1]
43+
workflow = parts[4]
44+
45+
query = parse_qs(parsed.query)
46+
params = {}
47+
if query.get("branch"):
48+
params["branch"] = query["branch"][0]
49+
if query.get("event"):
50+
params["event"] = query["event"][0]
51+
52+
base = f"{_RASTER_SHIELDS_BASE}/github/actions/workflow/status/{owner}/{repo}/{workflow}.png"
53+
if params:
54+
base = f"{base}?{urlencode(params)}"
55+
return base
56+
57+
58+
def _should_rasterize(parsed) -> bool:
59+
path = parsed.path.lower()
60+
if path.endswith(".svg"):
61+
return True
62+
return False
63+
64+
65+
def _wrap_weserv(url: str) -> str:
66+
encoded = quote(url, safe="")
67+
return f"{_WESERV_BASE}?url={encoded}&output=png"

src/md2bbcode/renderers/bbcode.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
from mistune.core import BaseRenderer
22
from mistune.util import escape as escape_text, striptags, safe_entity
3+
import re
34
from urllib.parse import urljoin, urlparse
45

6+
from md2bbcode.image_rewrite import rewrite_svg_url
7+
58

69
class BBCodeRenderer(BaseRenderer):
710
"""A renderer for converting Markdown to BBCode."""
@@ -56,7 +59,12 @@ def link(self, text: str, url: str, title=None) -> str:
5659

5760
def image(self, text: str, url: str, title=None) -> str:
5861
alt_text = f' alt="{text}"' if text else ''
59-
img_tag = f'[img{alt_text}]' + self.safe_url(url) + '[/img]'
62+
safe_url = self.safe_url(url)
63+
rewritten_url = rewrite_svg_url(safe_url)
64+
if rewritten_url is None:
65+
link_text = text or safe_url
66+
return f"[url={safe_url}]{link_text}[/url]"
67+
img_tag = f'[img{alt_text}]' + rewritten_url + '[/img]'
6068
# Check if alt text starts with 'pixel' and treat it as pixel art
6169
if text and text.lower().startswith('pixel'):
6270
return f'[pixelate]{img_tag}[/pixelate]'
@@ -115,6 +123,16 @@ def block_code(self, code: str, **attrs) -> str:
115123
return f"[CODE]{escape_text(code)}[/CODE]\n"
116124

117125
def block_quote(self, text: str) -> str:
126+
# GFMD "alerts"/admonitions are expressed as a blockquote
127+
# Render these into a dedicated XenForo custom BBCode, rather than a normal QUOTE.
128+
m = re.match(r"^\s*\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\]\s*", text, flags=re.IGNORECASE)
129+
if m:
130+
kind = m.group(1).lower()
131+
body = text[m.end():].strip()
132+
if body:
133+
return f"[admonition={kind}]{body}[/admonition]\n"
134+
return f"[admonition={kind}][/admonition]\n"
135+
118136
return '[QUOTE]\n' + text + '[/QUOTE]\n'
119137

120138
def block_html(self, html: str) -> str:

tests/test_svg_rewrite.py

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,18 +29,6 @@ def test_html_svg_wraps_weserv():
2929
assert '[img alt="alt text"]' in lowered
3030

3131

32-
def test_redguides_sparkline_adds_png_format():
33-
sparkline_url = (
34-
"https://www.redguides.com/community/resources/ghoul-resource.1623/watchers-sparkline"
35-
"?months=24&w=500&h=180"
36-
)
37-
markdown = f'<img src="{sparkline_url}" alt="Watchers">'
38-
result = process_readme(markdown, domain="")
39-
lowered = result.lower()
40-
41-
assert "watchers-sparkline?months=24&w=500&h=180&format=png" in lowered
42-
43-
4432
def test_non_svg_image_unchanged():
4533
png_url = "https://example.com/x.png"
4634
markdown = f"![alt]({png_url})"

0 commit comments

Comments
 (0)