mdx_neboard.py
244 lines
| 6.1 KiB
| text/x-python
|
PythonLexer
/ boards / mdx_neboard.py
neko259
|
r699 | # coding=utf-8 | ||
neko259
|
r736 | import re | ||
neko259
|
r1458 | import random | ||
neko259
|
r736 | 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
|
r1068 | LINE_BREAK_HTML = '<div class="br"></div>' | ||
neko259
|
r1460 | SPOILER_SPACE = ' ' | ||
MAX_SPOILER_MULTIPLIER = 2 | ||||
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): | ||
neko259
|
r1068 | name = '>q' | ||
preview_left = '<span class="quote">' | ||||
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 | |||
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
|
r1309 | result = post.get_link_view() | ||
neko259
|
r1061 | except ObjectDoesNotExist: | ||
pass | ||||
return result | ||||
neko259
|
r90 | |||
neko259
|
r52 | |||
neko259
|
r755 | def render_quote(tag_name, value, options, parent, context): | ||
neko259
|
r831 | source = '' | ||
neko259
|
r755 | if 'source' in options: | ||
source = options['source'] | ||||
neko259
|
r1398 | elif 'quote' in options: | ||
source = options['quote'] | ||||
neko259
|
r755 | |||
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
|
r1068 | # Insert a ">" at the start of every line | ||
result = '<span class="quote">>{}</span>'.format( | ||||
value.replace(LINE_BREAK_HTML, | ||||
'{}>'.format(LINE_BREAK_HTML))) | ||||
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
|
r1202 | def render_tag(tag_name, value, options, parent, context): | ||
tag_name = value.lower() | ||||
try: | ||||
url = boards.models.Tag.objects.get(name=tag_name).get_view() | ||||
except ObjectDoesNotExist: | ||||
url = tag_name | ||||
return url | ||||
neko259
|
r1458 | def render_spoiler(tag_name, value, options, parent, context): | ||
text_len = len(value) | ||||
neko259
|
r1460 | space_count = random.randint(0, text_len * MAX_SPOILER_MULTIPLIER) | ||
side_spaces = SPOILER_SPACE * (space_count // 2) | ||||
neko259
|
r1459 | return '<span class="spoiler">{}{}{}</span>'.format(side_spaces, value, | ||
side_spaces) | ||||
neko259
|
r1458 | |||
return quote_element | ||||
neko259
|
r438 | formatters = [ | ||
QuotePattern, | ||||
SpoilerPattern, | ||||
ItalicPattern, | ||||
BoldPattern, | ||||
CommentPattern, | ||||
StrikeThroughPattern, | ||||
neko259
|
r440 | CodePattern, | ||
neko259
|
r438 | ] | ||
neko259
|
r1066 | |||
PREPARSE_PATTERNS = { | ||||
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. | ||||
neko259
|
r1068 | self.parser = bbcode.Parser(newline=LINE_BREAK_HTML) | ||
neko259
|
r1066 | |||
self.parser.add_formatter('post', render_reflink, strip=True) | ||||
self.parser.add_formatter('quote', render_quote, strip=True) | ||||
self.parser.add_formatter('user', render_notification, strip=True) | ||||
neko259
|
r1202 | self.parser.add_formatter('tag', render_tag, strip=True) | ||
neko259
|
r1458 | self.parser.add_formatter('spoiler', render_spoiler, strip=True) | ||
neko259
|
r1066 | self.parser.add_simple_formatter( | ||
'comment', '<span class="comment">//%(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): | ||||
neko259
|
r1067 | return self.parser.format(text) | ||
neko259
|
r1499 | |||
parser = Parser() | ||||
def get_parser(): | ||||
return parser | ||||