forked from mozilla/kitsune
/
parser.py
209 lines (165 loc) · 6.85 KB
/
parser.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
from django.conf import settings
import jingo
from tower import ugettext_lazy as _lazy
from wikimarkup.parser import Parser
from gallery.models import Image
from sumo.urlresolvers import reverse
from wiki.models import Document
ALLOWED_ATTRIBUTES = {
'a': ['href', 'title', 'class', 'rel'],
'div': ['id', 'class', 'style', 'data-for'],
'h1': ['id'],
'h2': ['id'],
'h3': ['id'],
'h4': ['id'],
'h5': ['id'],
'h6': ['id'],
'li': ['class'],
'span': ['class', 'data-for'],
'img': ['class', 'src', 'alt', 'title', 'height', 'width', 'style'],
'video': ['height', 'width', 'controls', 'data-fallback'],
'source': ['src', 'type'],
}
IMAGE_PARAMS = ['alt', 'align', 'caption', 'valign', 'frame', 'page', 'link',
'width', 'height']
IMAGE_PARAM_VALUES = {
'align': ('none', 'left', 'center', 'right'),
'valign': ('baseline', 'sub', 'super', 'top', 'text-top', 'middle',
'bottom', 'text-bottom'),
}
def wiki_to_html(wiki_markup, locale=settings.WIKI_DEFAULT_LANGUAGE):
"""Wiki Markup -> HTML"""
return WikiParser().parse(wiki_markup, show_toc=False, locale=locale)
def get_object_fallback(cls, title, locale, default=None, **kwargs):
"""Returns an instance of cls matching title, locale, or falls back.
If the fallback fails, the return value is default.
You may pass in additional kwargs which go straight to the query.
"""
try:
return cls.objects.get(title=title, locale=locale, **kwargs)
except cls.DoesNotExist:
pass
# Fallback
try:
fallback = cls.objects.get(
title=title, locale=settings.WIKI_DEFAULT_LANGUAGE, **kwargs)
if hasattr(fallback, 'translated_to'):
trans = fallback.translated_to(locale)
if trans:
return trans
return fallback
# Okay, all else failed
except cls.DoesNotExist:
return default
def _get_wiki_link(title, locale):
"""Checks the page exists, and returns its URL or the URL to create it.
Return value is a dict: {'found': boolean, 'url': string}.
found is False if the document does not exist.
"""
d = get_object_fallback(Document, locale=locale, title=title,
is_template=False)
if d:
return {'found': True, 'url': d.get_absolute_url(), 'text': d.title}
# To avoid circular imports, wiki.models imports wiki_to_html
from sumo.helpers import urlparams
return {'found': False,
'text': title,
'url': urlparams(reverse('wiki.new_document', locale=locale),
title=title)}
def build_hook_params(string, locale, allowed_params=[],
allowed_param_values={}):
"""Parses a string of the form 'some-title|opt1|opt2=arg2|opt3...'
Builds a list of items and returns relevant parameters in a dict.
"""
if not '|' in string: # No params? Simple and easy.
string = string.strip()
return (string, {'alt': string})
items = [i.strip() for i in string.split('|')]
title = items.pop(0)
params = {}
last_item = ''
for item in items: # this splits by = or assigns the dict key to True
if '=' in item:
param, value = item.split('=', 1)
params[param] = value
else:
params[item] = True
last_item = item
if 'caption' in allowed_params:
params['caption'] = title
# Allowed parameters are not caption. All else is.
if last_item and last_item not in allowed_params:
params['caption'] = items.pop()
del params[last_item]
elif last_item == 'caption':
params['caption'] = last_item
# Validate params allowed
for p in params.keys():
if p not in allowed_params:
del params[p]
# Validate params with limited # of values
for p in allowed_param_values:
if p in params and params[p] not in allowed_param_values[p]:
del params[p]
# Handle page as a special case
if 'page' in params and params['page'] is not True:
link = _get_wiki_link(params['page'], locale)
params['link'] = link['url']
params['found'] = link['found']
return (title, params)
class WikiParser(Parser):
"""Wrapper for wikimarkup which adds Kitsune-specific callbacks and setup.
"""
def __init__(self, base_url=None):
super(WikiParser, self).__init__(base_url)
# Register default hooks
self.registerInternalLinkHook(None, self._hook_internal_link)
self.registerInternalLinkHook('Image', self._hook_image_tag)
def parse(self, text, show_toc=None, tags=None, attributes=None,
locale=settings.WIKI_DEFAULT_LANGUAGE):
"""Given wiki markup, return HTML.
Pass a locale to get all the hooks to look up Documents or Media
(Video, Image) for that locale. We key Documents by title and locale,
so both are required to identify it for a e.g. link.
Since py-wikimarkup's hooks don't offer custom paramters for callbacks,
we're using self.locale to keep things simple."""
self.locale = locale
parser_kwargs = {'tags': tags} if tags else {}
return super(WikiParser, self).parse(text, show_toc=show_toc,
attributes=attributes or ALLOWED_ATTRIBUTES, **parser_kwargs)
def _hook_internal_link(self, parser, space, name):
"""Parses text and returns internal link."""
text = False
title = name
# Split on pipe -- [[href|name]]
if '|' in name:
title, text = title.split('|', 1)
hash = ''
if '#' in title:
title, hash = title.split('#', 1)
# Sections use _, page names use +
if hash != '':
hash = '#' + hash.replace(' ', '_')
# Links to this page can just contain href="#hash"
if title == '' and hash != '':
if not text:
text = hash.replace('_', ' ')
return u'<a href="%s">%s</a>' % (hash, text)
link = _get_wiki_link(title, self.locale)
a_cls = ''
if not link['found']:
a_cls = ' class="new"'
if not text:
text = link['text']
return u'<a href="%s%s"%s>%s</a>' % (link['url'], hash, a_cls, text)
def _hook_image_tag(self, parser, space, name):
"""Adds syntax for inserting images."""
title, params = build_hook_params(name, self.locale, IMAGE_PARAMS,
IMAGE_PARAM_VALUES)
message = _lazy('The image "%s" does not exist.') % title
image = get_object_fallback(Image, title, self.locale, message)
if isinstance(image, basestring):
return image
template = jingo.env.get_template('wikiparser/hook_image.html')
r_kwargs = {'image': image, 'params': params}
return template.render(**r_kwargs)