# coding=utf-8 import re import random import bbcode from urllib.parse import unquote from django.core.exceptions import ObjectDoesNotExist from django.core.urlresolvers import reverse import boards __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 = '
' SPOILER_SPACE = ' ' MAX_SPOILER_MULTIPLIER = 2 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 = '' preview_right = '' format_left = '[quote]' format_right = '[/quote]' class SpoilerPattern(TextFormatter): name = 'spoiler' preview_left = '' preview_right = '' format_left = '[spoiler]' format_right = '[/spoiler]' class CommentPattern(TextFormatter): name = '' preview_left = '// ' preview_right = '' format_left = '[comment]' format_right = '[/comment]' # TODO Use tag here class StrikeThroughPattern(TextFormatter): name = 's' preview_left = '' preview_right = '' format_left = '[s]' format_right = '[/s]' class ItalicPattern(TextFormatter): name = 'i' preview_left = '' preview_right = '' format_left = '[i]' format_right = '[/i]' class BoldPattern(TextFormatter): name = 'b' preview_left = '' preview_right = '' format_left = '[b]' format_right = '[/b]' class CodePattern(TextFormatter): name = 'code' preview_left = '' preview_right = '' format_left = '[code]' format_right = '[/code]' class HintPattern(TextFormatter): name = 'hint' preview_left = '' preview_right = '' 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 = '
%s
%s
' % (source, value) else: # Insert a ">" at the start of every line result = '>{}'.format( value.replace(LINE_BREAK_HTML, '{}>'.format(LINE_BREAK_HTML))) return result def render_hint(tag_name, value, options, parent, context): if 'hint' in options: hint = options['hint'] result = '{}'.format(hint, value) else: result = value return result def render_notification(tag_name, value, options, parent, content): username = value.lower() return '@{}'.format( reverse('notifications', kwargs={'username': username}), username) 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 def render_spoiler(tag_name, value, options, parent, context): text_len = len(value) space_count = random.randint(0, text_len * MAX_SPOILER_MULTIPLIER) side_spaces = SPOILER_SPACE * (space_count // 2) return '{}{}{}'.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" } 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', '// %(value)s', strip=True) self.parser.add_simple_formatter( 's', '%(value)s') self.parser.add_simple_formatter('code', '
%(value)s
', 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