mdx_neboard.py
232 lines
| 6.0 KiB
| text/x-python
|
PythonLexer
/ boards / mdx_neboard.py
neko259
|
r699 | # coding=utf-8 | |
neko259
|
r736 | import re | |
import bbcode | |||
neko259
|
r1066 | ||
from urllib.parse import unquote | |||
neko259
|
r1061 | from django.core.exceptions import ObjectDoesNotExist | |
neko259
|
r990 | from django.core.urlresolvers import reverse | |
neko259
|
r721 | ||
neko259
|
r98 | import boards | |
neko259
|
r52 | ||
neko259
|
r721 | ||
neko259
|
r89 | __author__ = 'neko259' | |
neko259
|
r52 | ||
neko259
|
r799 | REFLINK_PATTERN = re.compile(r'^\d+$') | |
neko259
|
r754 | MULTI_NEWLINES_PATTERN = re.compile(r'(\r?\n){2,}') | |
ONE_NEWLINE = '\n' | |||
neko259
|
r1066 | REGEX_URL = re.compile(r'https?\://[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(/\S*)?') | |
neko259
|
r302 | ||
neko259
|
r398 | ||
neko259
|
r438 | class TextFormatter(): | |
""" | |||
An interface for formatter that can be used in the text format panel | |||
""" | |||
neko259
|
r721 | def __init__(self): | |
pass | |||
neko259
|
r438 | name = '' | |
# Left and right tags for the button preview | |||
preview_left = '' | |||
preview_right = '' | |||
# Left and right characters for the textarea input | |||
format_left = '' | |||
format_right = '' | |||
neko259
|
r736 | class AutolinkPattern(): | |
neko259
|
r52 | def handleMatch(self, m): | |
link_element = etree.Element('a') | |||
href = m.group(2) | |||
link_element.set('href', href) | |||
link_element.text = href | |||
return link_element | |||
neko259
|
r736 | class QuotePattern(TextFormatter): | |
name = 'q' | |||
preview_left = '<span class="multiquote">' | |||
neko259
|
r438 | preview_right = '</span>' | |
neko259
|
r736 | format_left = '[quote]' | |
format_right = '[/quote]' | |||
neko259
|
r52 | ||
neko259
|
r736 | class SpoilerPattern(TextFormatter): | |
name = 'spoiler' | |||
neko259
|
r438 | preview_left = '<span class="spoiler">' | |
preview_right = '</span>' | |||
neko259
|
r736 | format_left = '[spoiler]' | |
format_right = '[/spoiler]' | |||
neko259
|
r438 | ||
neko259
|
r56 | def handleMatch(self, m): | |
quote_element = etree.Element('span') | |||
quote_element.set('class', 'spoiler') | |||
quote_element.text = m.group(2) | |||
return quote_element | |||
neko259
|
r736 | class CommentPattern(TextFormatter): | |
neko259
|
r438 | name = '' | |
preview_left = '<span class="comment">// ' | |||
preview_right = '</span>' | |||
neko259
|
r736 | format_left = '[comment]' | |
format_right = '[/comment]' | |||
neko259
|
r56 | ||
neko259
|
r755 | # TODO Use <s> tag here | |
neko259
|
r736 | class StrikeThroughPattern(TextFormatter): | |
neko259
|
r438 | name = 's' | |
preview_left = '<span class="strikethrough">' | |||
preview_right = '</span>' | |||
neko259
|
r736 | format_left = '[s]' | |
format_right = '[/s]' | |||
neko259
|
r330 | ||
neko259
|
r438 | class ItalicPattern(TextFormatter): | |
name = 'i' | |||
preview_left = '<i>' | |||
preview_right = '</i>' | |||
neko259
|
r736 | format_left = '[i]' | |
format_right = '[/i]' | |||
neko259
|
r438 | ||
class BoldPattern(TextFormatter): | |||
name = 'b' | |||
preview_left = '<b>' | |||
preview_right = '</b>' | |||
neko259
|
r736 | format_left = '[b]' | |
format_right = '[/b]' | |||
neko259
|
r438 | ||
neko259
|
r440 | class CodePattern(TextFormatter): | |
name = 'code' | |||
preview_left = '<code>' | |||
preview_right = '</code>' | |||
neko259
|
r736 | format_left = '[code]' | |
format_right = '[/code]' | |||
neko259
|
r699 | ||
neko259
|
r736 | def render_reflink(tag_name, value, options, parent, context): | |
neko259
|
r1061 | result = '>>%s' % value | |
neko259
|
r100 | ||
neko259
|
r1061 | if REFLINK_PATTERN.match(value): | |
post_id = int(value) | |||
neko259
|
r100 | ||
neko259
|
r1061 | try: | |
post = boards.models.Post.objects.get(id=post_id) | |||
neko259
|
r52 | ||
neko259
|
r1061 | result = '<a href="%s">>>%s</a>' % (post.get_url(), post_id) | |
except ObjectDoesNotExist: | |||
pass | |||
return result | |||
neko259
|
r90 | ||
neko259
|
r52 | ||
neko259
|
r1060 | def render_multithread(tag_name, value, options, parent, context): | |
result = '>>>%s' % value | |||
if REFLINK_PATTERN.match(value): | |||
post_id = int(value) | |||
neko259
|
r1061 | try: | |
post = boards.models.Post.objects.get(id=post_id) | |||
neko259
|
r1060 | ||
if post.is_opening(): | |||
result = '<a href="%s">>>>%s</a>' % (post.get_url(), post_id) | |||
neko259
|
r1061 | except ObjectDoesNotExist: | |
pass | |||
neko259
|
r1060 | ||
return result | |||
neko259
|
r755 | def render_quote(tag_name, value, options, parent, context): | |
neko259
|
r831 | source = '' | |
neko259
|
r755 | if 'source' in options: | |
source = options['source'] | |||
if source: | |||
neko259
|
r831 | result = '<div class="multiquote"><div class="quote-header">%s</div><div class="quote-text">%s</div></div>' % (source, value) | |
neko259
|
r755 | else: | |
neko259
|
r831 | result = '<div class="multiquote"><div class="quote-text">%s</div></div>' % value | |
neko259
|
r755 | ||
return result | |||
neko259
|
r990 | def render_notification(tag_name, value, options, parent, content): | |
neko259
|
r1008 | username = value.lower() | |
neko259
|
r990 | return '<a href="{}" class="user-cast">@{}</a>'.format( | |
neko259
|
r1008 | reverse('notifications', kwargs={'username': username}), username) | |
neko259
|
r990 | ||
neko259
|
r438 | formatters = [ | |
QuotePattern, | |||
SpoilerPattern, | |||
ItalicPattern, | |||
BoldPattern, | |||
CommentPattern, | |||
StrikeThroughPattern, | |||
neko259
|
r440 | CodePattern, | |
neko259
|
r438 | ] | |
neko259
|
r1066 | ||
PREPARSE_PATTERNS = { | |||
r'>>>(\d+)': r'[thread]\1[/thread]', # Multi-thread post ">>>123" | |||
r'(?<!>)>>(\d+)': r'[post]\1[/post]', # Reflink ">>123" | |||
r'^>([^>].+)': r'[quote]\1[/quote]', # Quote ">text" | |||
r'^//(.+)': r'[comment]\1[/comment]', # Comment "//text" | |||
r'\B@(\w+)': r'[user]\1[/user]', # User notification "@user" | |||
} | |||
class Parser: | |||
def __init__(self): | |||
# The newline hack is added because br's margin does not work in all | |||
# browsers except firefox, when the div's does. | |||
self.parser = bbcode.Parser(newline='<div class="br"></div>') | |||
self.parser.add_formatter('post', render_reflink, strip=True) | |||
self.parser.add_formatter('thread', render_multithread, strip=True) | |||
self.parser.add_formatter('quote', render_quote, strip=True) | |||
self.parser.add_formatter('user', render_notification, strip=True) | |||
self.parser.add_simple_formatter( | |||
'comment', '<span class="comment">//%(value)s</span>') | |||
self.parser.add_simple_formatter( | |||
'spoiler', '<span class="spoiler">%(value)s</span>') | |||
self.parser.add_simple_formatter( | |||
's', '<span class="strikethrough">%(value)s</span>') | |||
# TODO Why not use built-in tag? | |||
self.parser.add_simple_formatter('code', | |||
'<pre><code>%(value)s</pre></code>', | |||
render_embedded=False) | |||
def preparse(self, text): | |||
""" | |||
Performs manual parsing before the bbcode parser is used. | |||
Preparsed text is saved as raw and the text before preparsing is lost. | |||
""" | |||
new_text = MULTI_NEWLINES_PATTERN.sub(ONE_NEWLINE, text) | |||
for key, value in PREPARSE_PATTERNS.items(): | |||
new_text = re.sub(key, value, new_text, flags=re.MULTILINE) | |||
for link in REGEX_URL.findall(text): | |||
new_text = new_text.replace(link, unquote(link)) | |||
return new_text | |||
def parse(self, text): | |||
return self.parser.format(text) |