PCv5/app/utils/markdown_extensions/media.py

164 lines
4.8 KiB
Python

from markdown.extensions import Extension
from markdown.inlinepatterns import LinkInlineProcessor
import xml.etree.ElementTree as etree
class MediaExtension(Extension):
def __init__(self, **kwargs):
super().__init__(**kwargs)
def extendMarkdown(self, md):
self.md = md
# Override image detection
MEDIA_RE = r'\!\['
media_processor = MediaInlineProcessor(MEDIA_RE)
media_processor.md = md
md.inlinePatterns.register(media_processor, 'media_link', 155)
class AttrDict:
def __init__(self, attrs):
self.attrs = attrs
def has(self, name):
return name in self.attrs
def getString(self, name):
for attr in self.attrs:
if attr.startswith(name + "="):
return attr[len(name)+1:]
def getInt(self, name):
try:
s = self.getString(name)
return int(s) if s is not None else None
except ValueError:
return None
def getSize(self, name):
s = self.getString(name)
if s is None:
return None, None
dims = s.split("x", 1)
try:
if len(dims) == 1:
return int(dims[0]), None
else:
w = int(dims[0]) if dims[0] else None
h = int(dims[1]) if dims[1] else None
return w, h
except ValueError:
return None, None
class AttributeLinkInlineProcessor(LinkInlineProcessor):
"""
A LinkInlineProcessor which additionally supports attributes after links,
with the bracket syntax `{ <item>, <item>, ... }` where each item is a
key/value pair with either single or double quotes: `key='value'` or
`key="value"`.
"""
def getAttributes(self, data, index):
current_quote = ""
has_closing_brace = False
attrs = []
current_attr_text = ""
if index >= len(data) or data[index] != '{':
return AttrDict([]), index, True
index += 1
for pos in range(index, len(data)):
c = data[pos]
index += 1
# Close quote
if current_quote != "" and c == current_quote:
current_quote = ""
continue
# Open new quote
if current_quote == "" and c in ["'", '"']:
current_quote = c
continue
# Close brace
if current_quote == "" and c == "}":
has_closing_brace = True
break
if current_quote == "" and c == " ":
if current_attr_text:
attrs.append(current_attr_text)
current_attr_text = ""
else:
current_attr_text += c
if current_attr_text:
attrs.append(current_attr_text)
return AttrDict(attrs), index, has_closing_brace
class MediaInlineProcessor(AttributeLinkInlineProcessor):
""" Return a media element from the given match. """
def isVideo(self, url):
# TODO: YouTube integration
return url.endswith(".mp4") or url.endswith(".webm")
def isAudio(self, url):
return url.endswith(".mp3") or url.endswith(".ogg")
def handleMatch(self, m, data):
text, index, handled = self.getText(data, m.end(0))
if not handled:
return None, None, None
src, title, index, handled = self.getLink(data, index)
if not handled:
return None, None, None
attrs, index, handled = self.getAttributes(data, index)
if not handled:
return None, None, None
kind = "image"
if attrs.has("audio") or self.isAudio(src):
kind = "audio"
elif attrs.has("video") or self.isVideo(src):
kind = "video"
# Images
if kind == "image":
w, h = attrs.getSize("size")
class_ = ""
# TODO: Media converter: Find a way to clear atfer a float
if attrs.has("pixelated"):
class_ += " pixelated"
if attrs.has("left"):
class_ += " align-left"
if attrs.has("center"):
class_ += " align-center"
elif attrs.has("right"):
class_ += " align-right"
elif attrs.has("float-left"):
class_ += " float-left"
elif attrs.has("float-right"):
class_ += " float-right"
el = etree.Element("img")
el.set("src", src)
if title is not None:
el.set("title", title)
if class_ != "":
el.set("class", class_)
if w is not None:
el.set("width", str(w))
if h is not None:
el.set("height", str(h))
el.set('alt', self.unescape(text))
return el, m.start(0), index
return None, None, None