##// END OF EJS Templates
Version bump
Version bump

File last commit:

r2048:7a57822e default
r2109:2f9d7bc7 4.11.0 default
Show More
mdx_neboard.py
292 lines | 7.7 KiB | text/x-python | PythonLexer
# coding=utf-8
from xml import etree
import re
import random
import bbcode
from urllib.parse import unquote
from django.core.exceptions import ObjectDoesNotExist
from django.urls import reverse
import boards
from boards import settings
from neboard.settings import ALLOWED_HOSTS
__author__ = 'neko259'
REFLINK_PATTERN = re.compile(r'^\d+$')
GLOBAL_REFLINK_PATTERN = re.compile(r'(\w+)::([^:]+)::(\d+)')
MULTI_NEWLINES_PATTERN = re.compile(r'(\r?\n){2,}')
ONE_NEWLINE = '\n'
REGEX_URL = re.compile(r'https?\://[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(/\S*)?')
LINE_BREAK_HTML = '<div class="br"></div>'
SPOILER_SPACE = '&nbsp;'
MAX_SPOILER_MULTIPLIER = 2
MAX_SPOILER_SPACE_COUNT = 20
class TextFormatter:
"""
An interface for formatter that can be used in the text format panel
"""
def __init__(self):
pass
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 = ''
class AutolinkPattern:
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
class QuotePattern(TextFormatter):
name = '>q'
preview_left = '<span class="quote">'
preview_right = '</span>'
format_left = '[quote]'
format_right = '[/quote]'
class SpoilerPattern(TextFormatter):
name = 'spoiler'
preview_left = '<span class="spoiler">'
preview_right = '</span>'
format_left = '[spoiler]'
format_right = '[/spoiler]'
class CommentPattern(TextFormatter):
name = ''
preview_left = '<span class="comment">// '
preview_right = '</span>'
format_left = '[comment]'
format_right = '[/comment]'
# TODO Use <s> tag here
class StrikeThroughPattern(TextFormatter):
name = 's'
preview_left = '<span class="strikethrough">'
preview_right = '</span>'
format_left = '[s]'
format_right = '[/s]'
class ItalicPattern(TextFormatter):
name = 'i'
preview_left = '<i>'
preview_right = '</i>'
format_left = '[i]'
format_right = '[/i]'
class BoldPattern(TextFormatter):
name = 'b'
preview_left = '<b>'
preview_right = '</b>'
format_left = '[b]'
format_right = '[/b]'
class CodePattern(TextFormatter):
name = 'code'
preview_left = '<code>'
preview_right = '</code>'
format_left = '[code]'
format_right = '[/code]'
class HintPattern(TextFormatter):
name = 'hint'
preview_left = '<span class="hint">'
preview_right = '</span>'
format_left = '[hint]'
format_right = '[/hint]'
def render_reflink(tag_name, value, options, parent, context):
result = '>>%s' % value
post = None
if REFLINK_PATTERN.match(value):
post_id = int(value)
try:
post = boards.models.Post.objects.get(id=post_id)
except ObjectDoesNotExist:
pass
elif GLOBAL_REFLINK_PATTERN.match(value):
match = GLOBAL_REFLINK_PATTERN.search(value)
try:
global_id = boards.models.GlobalId.objects.get(
key_type=match.group(1), key=match.group(2),
local_id=match.group(3))
post = global_id.post
except ObjectDoesNotExist:
pass
if post is not None:
result = post.get_link_view()
return result
def render_quote(tag_name, value, options, parent, context):
source = ''
if 'source' in options:
source = options['source']
elif 'quote' in options:
source = options['quote']
if source:
result = '<div class="multiquote"><div class="quote-header">%s</div><div class="quote-text">%s</div></div>' % (source, value)
else:
# Insert a ">" at the start of every line
result = '<span class="quote">&gt;{}</span>'.format(
value.replace(LINE_BREAK_HTML,
'{}&gt;'.format(LINE_BREAK_HTML)))
return result
def render_hint(tag_name, value, options, parent, context):
if 'hint' in options:
hint = options['hint']
result = '<span class="hint" title="{}">{}</span>'.format(hint, value)
else:
result = value
return result
def render_notification(tag_name, value, options, parent, content):
username = value.lower()
return '<a href="{}" class="user-cast">@{}</a>'.format(
reverse('notifications', kwargs={'username': username}), username)
def render_tag(tag_name, value, options, parent, context):
tag_name = value.lower()
tag = boards.models.Tag.objects.get_by_alias(tag_name)
if tag:
url = tag.get_view()
else:
url = tag_name
return url
def render_spoiler(tag_name, value, options, parent, context):
if settings.get_bool('Forms', 'AdditionalSpoilerSpaces'):
text_len = len(value)
space_count = min(random.randint(0, text_len * MAX_SPOILER_MULTIPLIER),
MAX_SPOILER_SPACE_COUNT)
side_spaces = SPOILER_SPACE * (space_count // 2)
else:
side_spaces = ''
return '<span class="spoiler">{}{}{}</span>'.format(side_spaces,
value, side_spaces)
formatters = [
QuotePattern,
SpoilerPattern,
ItalicPattern,
BoldPattern,
CommentPattern,
StrikeThroughPattern,
CodePattern,
HintPattern,
]
PREPARSE_PATTERNS = {
r'(?<!>)>>(\d+)': r'[post]\1[/post]', # Reflink ">>123"
r'^>([^>].+)': r'[quote]\1[/quote]', # Quote ">text"
r'^//\s?(.+)': r'[comment]\1[/comment]', # Comment "//text"
r'\B@(\w+)': r'[user]\1[/user]', # User notification "@user"
}
for hostname in ALLOWED_HOSTS:
if hostname != '*':
PREPARSE_PATTERNS[r'https?://{}/thread/\d+/#(\d+)/?'.format(hostname)] = r'[post]\1[/post]'
PREPARSE_PATTERNS[r'https?://{}/thread/(\d+)/?'.format(hostname)] = r'[post]\1[/post]'
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=LINE_BREAK_HTML)
self.parser.add_formatter('post', render_reflink, strip=True)
self.parser.add_formatter('quote', render_quote, strip=True)
self.parser.add_formatter('hint', render_hint, strip=True)
self.parser.add_formatter('user', render_notification, strip=True)
self.parser.add_formatter('tag', render_tag, strip=True)
self.parser.add_formatter('spoiler', render_spoiler, strip=True)
self.parser.add_simple_formatter(
'comment', '<span class="comment">// %(value)s</span>', strip=True)
self.parser.add_simple_formatter(
's', '<span class="strikethrough">%(value)s</span>')
self.parser.add_simple_formatter('code',
'<pre><code>%(value)s</pre></code>',
render_embedded=False,
escape_html=True,
replace_links=False,
replace_cosmetic=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)
parser = Parser()
def get_parser():
return parser