164 lines
4.8 KiB
Python
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
|