##// END OF EJS Templates
Parse global reflinks
neko259 -
r1528:4c509fcc decentral
parent child Browse files
Show More
@@ -1,244 +1,254 b''
1 # coding=utf-8
1 # coding=utf-8
2
2
3 import re
3 import re
4 import random
4 import random
5 import bbcode
5 import bbcode
6
6
7 from urllib.parse import unquote
7 from urllib.parse import unquote
8
8
9 from django.core.exceptions import ObjectDoesNotExist
9 from django.core.exceptions import ObjectDoesNotExist
10 from django.core.urlresolvers import reverse
10 from django.core.urlresolvers import reverse
11
11
12 import boards
12 import boards
13
13
14
14
15 __author__ = 'neko259'
15 __author__ = 'neko259'
16
16
17
17
18 REFLINK_PATTERN = re.compile(r'^\d+$')
18 REFLINK_PATTERN = re.compile(r'^\d+$')
19 GLOBAL_REFLINK_PATTERN = re.compile(r'(\w+)::([^:]+)::(\d+)')
19 MULTI_NEWLINES_PATTERN = re.compile(r'(\r?\n){2,}')
20 MULTI_NEWLINES_PATTERN = re.compile(r'(\r?\n){2,}')
20 ONE_NEWLINE = '\n'
21 ONE_NEWLINE = '\n'
21 REGEX_URL = re.compile(r'https?\://[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(/\S*)?')
22 REGEX_URL = re.compile(r'https?\://[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(/\S*)?')
22 LINE_BREAK_HTML = '<div class="br"></div>'
23 LINE_BREAK_HTML = '<div class="br"></div>'
23 SPOILER_SPACE = '&nbsp;'
24 SPOILER_SPACE = '&nbsp;'
24
25
25 MAX_SPOILER_MULTIPLIER = 2
26 MAX_SPOILER_MULTIPLIER = 2
26
27
27
28
28 class TextFormatter():
29 class TextFormatter():
29 """
30 """
30 An interface for formatter that can be used in the text format panel
31 An interface for formatter that can be used in the text format panel
31 """
32 """
32
33
33 def __init__(self):
34 def __init__(self):
34 pass
35 pass
35
36
36 name = ''
37 name = ''
37
38
38 # Left and right tags for the button preview
39 # Left and right tags for the button preview
39 preview_left = ''
40 preview_left = ''
40 preview_right = ''
41 preview_right = ''
41
42
42 # Left and right characters for the textarea input
43 # Left and right characters for the textarea input
43 format_left = ''
44 format_left = ''
44 format_right = ''
45 format_right = ''
45
46
46
47
47 class AutolinkPattern():
48 class AutolinkPattern():
48 def handleMatch(self, m):
49 def handleMatch(self, m):
49 link_element = etree.Element('a')
50 link_element = etree.Element('a')
50 href = m.group(2)
51 href = m.group(2)
51 link_element.set('href', href)
52 link_element.set('href', href)
52 link_element.text = href
53 link_element.text = href
53
54
54 return link_element
55 return link_element
55
56
56
57
57 class QuotePattern(TextFormatter):
58 class QuotePattern(TextFormatter):
58 name = '>q'
59 name = '>q'
59 preview_left = '<span class="quote">'
60 preview_left = '<span class="quote">'
60 preview_right = '</span>'
61 preview_right = '</span>'
61
62
62 format_left = '[quote]'
63 format_left = '[quote]'
63 format_right = '[/quote]'
64 format_right = '[/quote]'
64
65
65
66
66 class SpoilerPattern(TextFormatter):
67 class SpoilerPattern(TextFormatter):
67 name = 'spoiler'
68 name = 'spoiler'
68 preview_left = '<span class="spoiler">'
69 preview_left = '<span class="spoiler">'
69 preview_right = '</span>'
70 preview_right = '</span>'
70
71
71 format_left = '[spoiler]'
72 format_left = '[spoiler]'
72 format_right = '[/spoiler]'
73 format_right = '[/spoiler]'
73
74
74
75
75 class CommentPattern(TextFormatter):
76 class CommentPattern(TextFormatter):
76 name = ''
77 name = ''
77 preview_left = '<span class="comment">// '
78 preview_left = '<span class="comment">// '
78 preview_right = '</span>'
79 preview_right = '</span>'
79
80
80 format_left = '[comment]'
81 format_left = '[comment]'
81 format_right = '[/comment]'
82 format_right = '[/comment]'
82
83
83
84
84 # TODO Use <s> tag here
85 # TODO Use <s> tag here
85 class StrikeThroughPattern(TextFormatter):
86 class StrikeThroughPattern(TextFormatter):
86 name = 's'
87 name = 's'
87 preview_left = '<span class="strikethrough">'
88 preview_left = '<span class="strikethrough">'
88 preview_right = '</span>'
89 preview_right = '</span>'
89
90
90 format_left = '[s]'
91 format_left = '[s]'
91 format_right = '[/s]'
92 format_right = '[/s]'
92
93
93
94
94 class ItalicPattern(TextFormatter):
95 class ItalicPattern(TextFormatter):
95 name = 'i'
96 name = 'i'
96 preview_left = '<i>'
97 preview_left = '<i>'
97 preview_right = '</i>'
98 preview_right = '</i>'
98
99
99 format_left = '[i]'
100 format_left = '[i]'
100 format_right = '[/i]'
101 format_right = '[/i]'
101
102
102
103
103 class BoldPattern(TextFormatter):
104 class BoldPattern(TextFormatter):
104 name = 'b'
105 name = 'b'
105 preview_left = '<b>'
106 preview_left = '<b>'
106 preview_right = '</b>'
107 preview_right = '</b>'
107
108
108 format_left = '[b]'
109 format_left = '[b]'
109 format_right = '[/b]'
110 format_right = '[/b]'
110
111
111
112
112 class CodePattern(TextFormatter):
113 class CodePattern(TextFormatter):
113 name = 'code'
114 name = 'code'
114 preview_left = '<code>'
115 preview_left = '<code>'
115 preview_right = '</code>'
116 preview_right = '</code>'
116
117
117 format_left = '[code]'
118 format_left = '[code]'
118 format_right = '[/code]'
119 format_right = '[/code]'
119
120
120
121
121 def render_reflink(tag_name, value, options, parent, context):
122 def render_reflink(tag_name, value, options, parent, context):
122 result = '>>%s' % value
123 result = '>>%s' % value
123
124
125 post = None
124 if REFLINK_PATTERN.match(value):
126 if REFLINK_PATTERN.match(value):
125 post_id = int(value)
127 post_id = int(value)
126
128
127 try:
129 try:
128 post = boards.models.Post.objects.get(id=post_id)
130 post = boards.models.Post.objects.get(id=post_id)
129
131
130 result = post.get_link_view()
131 except ObjectDoesNotExist:
132 except ObjectDoesNotExist:
132 pass
133 pass
134 elif GLOBAL_REFLINK_PATTERN.match(value):
135 match = GLOBAL_REFLINK_PATTERN.search(value)
136 try:
137 global_id = boards.models.GlobalId.objects.get(
138 key_type=match.group(1), key=match.group(2),
139 local_id=match.group(3))
140 post = global_id.post
141 except ObjectDoesNotExist:
142 pass
143
144 if post is not None:
145 result = post.get_link_view()
133
146
134 return result
147 return result
135
148
136
149
137 def render_quote(tag_name, value, options, parent, context):
150 def render_quote(tag_name, value, options, parent, context):
138 source = ''
151 source = ''
139 if 'source' in options:
152 if 'source' in options:
140 source = options['source']
153 source = options['source']
141 elif 'quote' in options:
154 elif 'quote' in options:
142 source = options['quote']
155 source = options['quote']
143
156
144 if source:
157 if source:
145 result = '<div class="multiquote"><div class="quote-header">%s</div><div class="quote-text">%s</div></div>' % (source, value)
158 result = '<div class="multiquote"><div class="quote-header">%s</div><div class="quote-text">%s</div></div>' % (source, value)
146 else:
159 else:
147 # Insert a ">" at the start of every line
160 # Insert a ">" at the start of every line
148 result = '<span class="quote">&gt;{}</span>'.format(
161 result = '<span class="quote">&gt;{}</span>'.format(
149 value.replace(LINE_BREAK_HTML,
162 value.replace(LINE_BREAK_HTML,
150 '{}&gt;'.format(LINE_BREAK_HTML)))
163 '{}&gt;'.format(LINE_BREAK_HTML)))
151
164
152 return result
165 return result
153
166
154
167
155 def render_notification(tag_name, value, options, parent, content):
168 def render_notification(tag_name, value, options, parent, content):
156 username = value.lower()
169 username = value.lower()
157
170
158 return '<a href="{}" class="user-cast">@{}</a>'.format(
171 return '<a href="{}" class="user-cast">@{}</a>'.format(
159 reverse('notifications', kwargs={'username': username}), username)
172 reverse('notifications', kwargs={'username': username}), username)
160
173
161
174
162 def render_tag(tag_name, value, options, parent, context):
175 def render_tag(tag_name, value, options, parent, context):
163 tag_name = value.lower()
176 tag_name = value.lower()
164
177
165 try:
178 try:
166 url = boards.models.Tag.objects.get(name=tag_name).get_view()
179 url = boards.models.Tag.objects.get(name=tag_name).get_view()
167 except ObjectDoesNotExist:
180 except ObjectDoesNotExist:
168 url = tag_name
181 url = tag_name
169
182
170 return url
183 return url
171
184
172
185
173 def render_spoiler(tag_name, value, options, parent, context):
186 def render_spoiler(tag_name, value, options, parent, context):
174 text_len = len(value)
187 text_len = len(value)
175 space_count = random.randint(0, text_len * MAX_SPOILER_MULTIPLIER)
188 space_count = random.randint(0, text_len * MAX_SPOILER_MULTIPLIER)
176 side_spaces = SPOILER_SPACE * (space_count // 2)
189 side_spaces = SPOILER_SPACE * (space_count // 2)
177 return '<span class="spoiler">{}{}{}</span>'.format(side_spaces, value,
190 return '<span class="spoiler">{}{}{}</span>'.format(side_spaces, value,
178 side_spaces)
191 side_spaces)
179
192
180 return quote_element
181
182
183
193
184 formatters = [
194 formatters = [
185 QuotePattern,
195 QuotePattern,
186 SpoilerPattern,
196 SpoilerPattern,
187 ItalicPattern,
197 ItalicPattern,
188 BoldPattern,
198 BoldPattern,
189 CommentPattern,
199 CommentPattern,
190 StrikeThroughPattern,
200 StrikeThroughPattern,
191 CodePattern,
201 CodePattern,
192 ]
202 ]
193
203
194
204
195 PREPARSE_PATTERNS = {
205 PREPARSE_PATTERNS = {
196 r'(?<!>)>>(\d+)': r'[post]\1[/post]', # Reflink ">>123"
206 r'(?<!>)>>(\d+)': r'[post]\1[/post]', # Reflink ">>123"
197 r'^>([^>].+)': r'[quote]\1[/quote]', # Quote ">text"
207 r'^>([^>].+)': r'[quote]\1[/quote]', # Quote ">text"
198 r'^//(.+)': r'[comment]\1[/comment]', # Comment "//text"
208 r'^//(.+)': r'[comment]\1[/comment]', # Comment "//text"
199 r'\B@(\w+)': r'[user]\1[/user]', # User notification "@user"
209 r'\B@(\w+)': r'[user]\1[/user]', # User notification "@user"
200 }
210 }
201
211
202
212
203 class Parser:
213 class Parser:
204 def __init__(self):
214 def __init__(self):
205 # The newline hack is added because br's margin does not work in all
215 # The newline hack is added because br's margin does not work in all
206 # browsers except firefox, when the div's does.
216 # browsers except firefox, when the div's does.
207 self.parser = bbcode.Parser(newline=LINE_BREAK_HTML)
217 self.parser = bbcode.Parser(newline=LINE_BREAK_HTML)
208
218
209 self.parser.add_formatter('post', render_reflink, strip=True)
219 self.parser.add_formatter('post', render_reflink, strip=True)
210 self.parser.add_formatter('quote', render_quote, strip=True)
220 self.parser.add_formatter('quote', render_quote, strip=True)
211 self.parser.add_formatter('user', render_notification, strip=True)
221 self.parser.add_formatter('user', render_notification, strip=True)
212 self.parser.add_formatter('tag', render_tag, strip=True)
222 self.parser.add_formatter('tag', render_tag, strip=True)
213 self.parser.add_formatter('spoiler', render_spoiler, strip=True)
223 self.parser.add_formatter('spoiler', render_spoiler, strip=True)
214 self.parser.add_simple_formatter(
224 self.parser.add_simple_formatter(
215 'comment', '<span class="comment">//%(value)s</span>')
225 'comment', '<span class="comment">//%(value)s</span>')
216 self.parser.add_simple_formatter(
226 self.parser.add_simple_formatter(
217 's', '<span class="strikethrough">%(value)s</span>')
227 's', '<span class="strikethrough">%(value)s</span>')
218 # TODO Why not use built-in tag?
228 # TODO Why not use built-in tag?
219 self.parser.add_simple_formatter('code',
229 self.parser.add_simple_formatter('code',
220 '<pre><code>%(value)s</pre></code>',
230 '<pre><code>%(value)s</pre></code>',
221 render_embedded=False)
231 render_embedded=False)
222
232
223 def preparse(self, text):
233 def preparse(self, text):
224 """
234 """
225 Performs manual parsing before the bbcode parser is used.
235 Performs manual parsing before the bbcode parser is used.
226 Preparsed text is saved as raw and the text before preparsing is lost.
236 Preparsed text is saved as raw and the text before preparsing is lost.
227 """
237 """
228 new_text = MULTI_NEWLINES_PATTERN.sub(ONE_NEWLINE, text)
238 new_text = MULTI_NEWLINES_PATTERN.sub(ONE_NEWLINE, text)
229
239
230 for key, value in PREPARSE_PATTERNS.items():
240 for key, value in PREPARSE_PATTERNS.items():
231 new_text = re.sub(key, value, new_text, flags=re.MULTILINE)
241 new_text = re.sub(key, value, new_text, flags=re.MULTILINE)
232
242
233 for link in REGEX_URL.findall(text):
243 for link in REGEX_URL.findall(text):
234 new_text = new_text.replace(link, unquote(link))
244 new_text = new_text.replace(link, unquote(link))
235
245
236 return new_text
246 return new_text
237
247
238 def parse(self, text):
248 def parse(self, text):
239 return self.parser.format(text)
249 return self.parser.format(text)
240
250
241
251
242 parser = Parser()
252 parser = Parser()
243 def get_parser():
253 def get_parser():
244 return parser
254 return parser
General Comments 0
You need to be logged in to leave comments. Login now