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
210
211
212
213
214
215 | """
Utilities for text-to-HTML conversion.
"""
def textile(text, **kwargs):
"""
Applies Textile conversion to a string, and returns the HTML.
This is simply a pass-through to the ``textile`` template filter
included in ``django.contrib.markup``, which works around issues
PyTextile has with Unicode strings. If you're not using Django but
want to use Textile with ``MarkupFormatter``, you'll need to
supply your own Textile filter.
"""
from django.contrib.markup.templatetags.markup import textile
return textile(text)
def markdown(text, **kwargs):
"""
Applies Markdown conversion to a string, and returns the HTML.
"""
import markdown
return markdown.markdown(text, **kwargs)
def restructuredtext(text, **kwargs):
"""
Applies reStructuredText conversion to a string, and returns the
HTML.
"""
from docutils import core
parts = core.publish_parts(source=text,
writer_name='html4css1',
**kwargs)
return parts['fragment']
DEFAULT_MARKUP_FILTERS = {
'textile': textile,
'markdown': markdown,
'restructuredtext': restructuredtext
}
class MarkupFormatter(object):
"""
Generic markup formatter which can handle multiple text-to-HTML
conversion systems.
Overview
========
Conversion is handled by filter functions registered with an
instance; a set of default filters is provided which cover
Markdown, reStructuredText and Textile (though using one of these
requires the appropriate module to be available on your system --
e.g., using the reST filter requires you to have ``docutils``
installed).
New filters can be added by registering them with an instance;
simply define a function which performs the conversion you want,
and use the ``register`` method to add it; ``register`` expects
two arguments:
1. The name to associate with the filter.
2. The actual filter function.
So, for example, you might define a new filter function called
``my_filter``, and register it like so::
formatter = MarkupFormatter()
formatter.register('my_filter', my_filter)
Instances are callable, so applying the conversion to a string is
simple::
my_html = formatter(my_string, filter_name='my_filter')
The filter to use for conversion is determined in either of two
ways:
1. If the keyword argument ``filter_name`` is supplied, it will be
used as the filter name.
2. Absent an explicit argument, the filter name will be taken from
the ``MARKUP_FILTER`` setting in your Django settings file (see
below).
Additionally, arbitrary keyword arguments can be supplied, and
they will be passed on to the filter function.
Reading default bahavior from a Django setting
==============================================
The Django setting ``MARKUP_FILTER`` can be used to specify
default behavior; if used, its value should be a 2-tuple:
* The first element should be the name of a filter.
* The second element should be a dictionary to use as keyword
arguments for that filter.
So, for example, to have the default behavior apply Markdown with
safe mode enabled, you would add this to your Django settings
file::
MARKUP_FILTER = ('markdown', { 'safe_mode': True })
The filter named in this setting does not have to be from the
default set; as long as you register a filter of that name before
trying to use the formatter, it will work.
To have the default behavior apply no conversion whatsoever, set
``MARKUP_FILTER`` like so::
MARKUP_FILTER = (None, {})
When the ``filter_name`` keyword argument is supplied, the
``MARKUP_FILTER`` setting is ignored entirely -- neither a filter
name nor any keyword arguments will be read from it. This means
that, by always supplying ``filter_name`` explicitly, it is
possible to use this formatter without configuring or even
installing Django.
Django and template autoescaping
================================
Django's template system defaults to escaping the output of
template variables, which can interfere with functions intended to
return HTML. ``MarkupFormatter`` does not in any way tamper with
Django's autoescaping, so pasing the results of formatting
directly to a Django template will result in that text being
escaped.
If you need to use ``MarkupFormatter`` for items which will be
passed to a Django template as variables, use the function
``django.utils.safestring.mark_safe`` to tell Django's template
system not to escape that text.
For convenience, a Django template filter is included (in
``templatetags/generic_markup.py``) which applies
``MarkupFormatter`` to a string and marks the result as not
requiring autoescaping.
Examples
========
Using the default behavior, with the filter name and arguments
taken from the ``MARKUP_FILTER`` setting::
formatter = MarkupFormatter()
my_string = 'Lorem ipsum dolor sit amet.\n\nConsectetuer adipiscing elit.'
my_html = formatter(my_string)
Explicitly naming the filter to use::
my_html = formatter(my_string, filter_name='markdown')
Passing keyword arguments::
my_html = formatter(my_string, filter_name='markdown', safe_mode=True)
Perform no conversion (return the text as-is)::
my_html = formatter(my_string, filter_name=None)
"""
def __init__(self):
self._filters = {}
for filter_name, filter_func in DEFAULT_MARKUP_FILTERS.items():
self.register(filter_name, filter_func)
def register(self, filter_name, filter_func):
"""
Registers a new filter for use.
"""
self._filters[filter_name] = filter_func
def __call__(self, text, **kwargs):
"""
Applies text-to-HTML conversion to a string, and returns the
HTML.
"""
if 'filter_name' in kwargs:
filter_name = kwargs['filter_name']
del kwargs['filter_name']
filter_kwargs = {}
else:
from django.conf import settings
filter_name, filter_kwargs = settings.MARKUP_FILTER
if filter_name is None:
return text
if filter_name not in self._filters:
raise ValueError("'%s' is not a registered markup filter. Registered filters are: %s." % (filter_name,
', '.join(self._filters.iterkeys())))
filter_func = self._filters[filter_name]
filter_kwargs.update(**kwargs)
return filter_func(text, **filter_kwargs)
# Unless you need to have multiple instances of MarkupFormatter lying
# around, or want to subclass it, the easiest way to use it is to
# import this instance.
formatter = MarkupFormatter()
|