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