2
Parser for HTML forms, that fills in defaults and errors. See
8
from formencode.rewritingparser import RewritingParser, html_quote
10
__all__ = ['render', 'htmlliteral', 'default_formatter',
11
'none_formatter', 'escape_formatter',
14
def render(form, defaults=None, errors=None, use_all_keys=False,
15
error_formatters=None, add_attributes=None,
16
auto_insert_errors=True, auto_error_formatter=None,
17
text_as_default=False, listener=None, encoding=None,
18
error_class='error', prefix_error=True,
21
Render the ``form`` (which should be a string) given the defaults
22
and errors. Defaults are the values that go in the input fields
23
(overwriting any values that are there) and errors are displayed
24
inline in the form (and also effect input classes). Returns the
27
If ``auto_insert_errors`` is true (the default) then any errors
28
for which ``<form:error>`` tags can't be found will be put just
29
above the associated input field, or at the top of the form if no
32
If ``use_all_keys`` is true, if there are any extra fields from
33
defaults or errors that couldn't be used in the form it will be an
36
``error_formatters`` is a dictionary of formatter names to
37
one-argument functions that format an error into HTML. Some
38
default formatters are provided if you don't provide this.
40
``error_class`` is the class added to input fields when there is
41
an error for that field.
43
``add_attributes`` is a dictionary of field names to a dictionary
44
of attribute name/values. If the name starts with ``+`` then the
45
value will be appended to any existing attribute (e.g.,
46
``{'+class': ' important'}``).
48
``auto_error_formatter`` is used to create the HTML that goes
49
above the fields. By default it wraps the error message in a span
52
If ``text_as_default`` is true (default false) then ``<input
53
type=unknown>`` will be treated as text inputs.
55
``listener`` can be an object that watches fields pass; the only
56
one currently is in ``htmlfill_schemabuilder.SchemaBuilder``
58
``encoding`` specifies an encoding to assume when mixing str and
59
unicode text in the template.
61
``prefix_error`` specifies if the HTML created by auto_error_formatter is
62
put before the input control (default) or after the control.
64
``force_defaults`` specifies if a field default is not given in
65
the ``defaults`` dictionary then the control associated with the
66
field should be set as an unsuccessful control. So checkboxes will
67
be cleared, radio and select controls will have no value selected,
68
and textareas will be emptied. This defaults to ``True``, which is
69
appropriate the defaults are the result of a form submission.
73
if auto_insert_errors and auto_error_formatter is None:
74
auto_error_formatter = default_formatter
76
defaults=defaults, errors=errors,
77
use_all_keys=use_all_keys,
78
error_formatters=error_formatters,
79
add_attributes=add_attributes,
80
auto_error_formatter=auto_error_formatter,
81
text_as_default=text_as_default,
82
listener=listener, encoding=encoding,
83
prefix_error=prefix_error,
84
error_class=error_class,
85
force_defaults=force_defaults,
92
class htmlliteral(object):
94
def __init__(self, html, text=None):
96
text = re.sub(r'<.*?>', '', html)
97
text = html.replace('>', '>')
98
text = html.replace('<', '<')
99
text = html.replace('"', '"')
100
# @@: Not very complete
108
return '<%s html=%r text=%r>' % (
109
self.__class__.__name__, self.html, self.text)
114
def default_formatter(error):
116
Formatter that escapes the error, wraps the error in a span with
117
class ``error-message``, and adds a ``<br>``
119
return '<span class="error-message">%s</span><br />\n' % html_quote(error)
121
def none_formatter(error):
123
Formatter that does nothing, no escaping HTML, nothin'
127
def escape_formatter(error):
129
Formatter that escapes HTML, no more.
131
return html_quote(error)
133
def escapenl_formatter(error):
135
Formatter that escapes HTML, and translates newlines to ``<br>``
137
error = html_quote(error)
138
error = error.replace('\n', '<br>\n')
141
def ignore_formatter(error):
143
Formatter that emits nothing, regardless of the error.
147
class FillingParser(RewritingParser):
149
Fills HTML with default values, as in a form.
153
>>> defaults = {'name': 'Bob Jones',
154
... 'occupation': 'Crazy Cultist',
155
... 'address': '14 W. Canal\nNew Guinea',
158
>>> parser = FillingParser(defaults)
159
>>> parser.feed('<input type="text" name="name" value="fill">\
160
... <select name="occupation"><option value="">Default</option>\
161
... <option value="Crazy Cultist">Crazy cultist</option>\
162
... </select> <textarea cols="20" style="width: 100%" name="address">An address\
163
... </textarea> <input type="radio" name="living" value="yes">\
164
... <input type="radio" name="living" value="no">\
165
... <input type="checkbox" name="nice_guy" checked="checked">')
167
>>> print parser.text() # doctest: +NORMALIZE_WHITESPACE
168
<input type="text" name="name" value="Bob Jones">
169
<select name="occupation">
170
<option value="">Default</option>
171
<option value="Crazy Cultist" selected="selected">Crazy cultist</option>
173
<textarea cols="20" style="width: 100%" name="address">14 W. Canal
174
New Guinea</textarea>
175
<input type="radio" name="living" value="yes">
176
<input type="radio" name="living" value="no" checked="checked">
177
<input type="checkbox" name="nice_guy">
181
default_encoding = 'utf8'
183
def __init__(self, defaults, errors=None, use_all_keys=False,
184
error_formatters=None, error_class='error',
185
add_attributes=None, listener=None,
186
auto_error_formatter=None,
187
text_as_default=False, encoding=None, prefix_error=True,
188
force_defaults=True):
189
RewritingParser.__init__(self)
192
self.source_pos = None
193
self.defaults = defaults
194
self.in_textarea = None
195
self.skip_textarea = False
196
self.last_textarea_name = None
197
self.in_select = None
198
self.skip_next = False
199
self.errors = errors or {}
200
if isinstance(self.errors, (str, unicode)):
201
self.errors = {None: self.errors}
203
self.skip_error = False
204
self.use_all_keys = use_all_keys
206
self.used_errors = {}
207
if error_formatters is None:
208
self.error_formatters = default_formatter_dict
210
self.error_formatters = error_formatters
211
self.error_class = error_class
212
self.add_attributes = add_attributes or {}
213
self.listener = listener
214
self.auto_error_formatter = auto_error_formatter
215
self.text_as_default = text_as_default
216
self.encoding = encoding
217
self.prefix_error = prefix_error
218
self.force_defaults = force_defaults
220
def str_compare(self, str1, str2):
222
Compare the two objects as strings (coercing to strings if necessary).
223
Also uses encoding to compare the strings.
225
if not isinstance(str1, basestring):
226
if hasattr(str1, '__unicode__'):
230
if type(str1) == type(str2):
232
if isinstance(str1, unicode):
233
str1 = str1.encode(self.encoding or self.default_encoding)
235
str2 = str2.encode(self.encoding or self.default_encoding)
239
self.handle_misc(None)
240
RewritingParser.close(self)
241
unused_errors = self.errors.copy()
242
for key in self.used_errors.keys():
243
if unused_errors.has_key(key):
244
del unused_errors[key]
245
if self.auto_error_formatter:
246
for key, value in unused_errors.items():
247
error_message = self.auto_error_formatter(value)
248
error_message = '<!-- for: %s -->\n%s' % (key, error_message)
249
self.insert_at_marker(
252
if self.use_all_keys:
253
unused = self.defaults.copy()
254
for key in self.used_keys.keys():
255
if unused.has_key(key):
258
"These keys from defaults were not used in the form: %s"
262
for key in unused_errors.keys():
263
error_text.append("%s: %s" % (key, self.errors[key]))
265
"These errors were not used in the form: %s" %
266
', '.join(error_text))
267
if self.encoding is not None:
269
for item in self._content:
270
if isinstance(item, str):
271
item = item.decode(self.encoding)
272
new_content.append(item)
273
self._content = new_content
274
self._text = self._get_text()
276
def skip_output(self):
277
return (self.in_textarea and self.skip_textarea) or self.skip_error
279
def add_key(self, key):
280
self.used_keys[key] = 1
282
def handle_starttag(self, tag, attrs, startend=False):
285
self.handle_input(attrs, startend)
286
elif tag == 'textarea':
287
self.handle_textarea(attrs)
288
elif tag == 'select':
289
self.handle_select(attrs)
290
elif tag == 'option':
291
self.handle_option(attrs)
293
elif tag == 'form:error':
294
self.handle_error(attrs)
296
elif tag == 'form:iferror':
297
self.handle_iferror(attrs)
302
self.listener.listen_input(self, tag, attrs)
304
def handle_endtag(self, tag):
306
if tag == 'textarea':
307
self.handle_end_textarea()
308
elif tag == 'select':
309
self.handle_end_select()
310
elif tag == 'form:iferror':
311
self.handle_end_iferror()
313
def handle_startendtag(self, tag, attrs):
314
return self.handle_starttag(tag, attrs, True)
316
def handle_iferror(self, attrs):
317
name = self.get_attr(attrs, 'name')
319
if name.startswith('not '):
321
name = name.split(None, 1)[1]
322
assert name, "Name attribute in <iferror> required (%s)" % self.getpos()
324
ok = self.errors.get(name)
328
self.skip_error = True
329
self.skip_next = True
331
def handle_end_iferror(self):
333
self.skip_error = False
334
self.skip_next = True
336
def handle_error(self, attrs):
337
name = self.get_attr(attrs, 'name')
338
formatter = self.get_attr(attrs, 'format') or 'default'
341
assert name is not None, (
342
"Name attribute in <form:error> required if not contained in "
343
"<form:iferror> (%i:%i)" % self.getpos())
344
error = self.errors.get(name, '')
346
error = self.error_formatters[formatter](error)
347
self.write_text(error)
348
self.skip_next = True
349
self.used_errors[name] = 1
351
def handle_input(self, attrs, startend):
352
t = (self.get_attr(attrs, 'type') or 'text').lower()
353
name = self.get_attr(attrs, 'name')
354
if self.prefix_error:
355
self.write_marker(name)
356
value = self.defaults.get(name)
357
if self.add_attributes.has_key(name):
358
for attr_name, attr_value in self.add_attributes[name].items():
359
if attr_name.startswith('+'):
360
attr_name = attr_name[1:]
361
self.set_attr(attrs, attr_name,
362
self.get_attr(attrs, attr_name, '')
365
self.set_attr(attrs, attr_name, attr_value)
367
and self.errors.get(self.get_attr(attrs, 'name'))):
368
self.add_class(attrs, self.error_class)
369
if t in ('text', 'hidden'):
370
if value is None and not self.force_defaults:
371
value = self.get_attr(attrs, 'value', '')
372
self.set_attr(attrs, 'value', value)
373
self.write_tag('input', attrs, startend)
374
self.skip_next = True
376
elif t == 'checkbox':
377
if self.force_defaults:
380
selected = self.get_attr(attrs, 'checked')
381
if not self.get_attr(attrs, 'value'):
383
elif self.selected_multiple(value,
384
self.get_attr(attrs, 'value', '')):
387
self.set_attr(attrs, 'checked', 'checked')
389
self.del_attr(attrs, 'checked')
390
self.write_tag('input', attrs, startend)
391
self.skip_next = True
394
if self.str_compare(value, self.get_attr(attrs, 'value', '')):
395
self.set_attr(attrs, 'checked', 'checked')
396
elif self.force_defaults or name in self.defaults:
397
self.del_attr(attrs, 'checked')
398
self.write_tag('input', attrs, startend)
399
self.skip_next = True
402
pass # don't skip next
403
elif t == 'password':
404
if value is None and not self.force_defaults:
405
value = value or self.get_attr(attrs, 'value', '')
406
self.set_attr(attrs, 'value', value)
407
self.write_tag('input', attrs, startend)
408
self.skip_next = True
411
self.write_tag('input', attrs, startend)
412
self.skip_next = True
414
elif t == 'submit' or t == 'reset' or t == 'button':
415
self.set_attr(attrs, 'value', value or
416
self.get_attr(attrs, 'value', ''))
417
self.write_tag('input', attrs, startend)
418
self.skip_next = True
420
elif self.text_as_default:
422
value = self.get_attr(attrs, 'value', '')
423
self.set_attr(attrs, 'value', value)
424
self.write_tag('input', attrs, startend)
425
self.skip_next = True
428
assert 0, "I don't know about this kind of <input>: %s (pos: %s)" \
430
if not self.prefix_error:
431
self.write_marker(name)
433
def handle_textarea(self, attrs):
434
name = self.get_attr(attrs, 'name')
435
if self.prefix_error:
436
self.write_marker(name)
438
and self.errors.get(name)):
439
self.add_class(attrs, self.error_class)
440
value = self.defaults.get(name, '')
441
if value or self.force_defaults:
442
self.write_tag('textarea', attrs)
443
self.write_text(html_quote(value))
444
self.write_text('</textarea>')
445
self.skip_textarea = True
446
self.in_textarea = True
447
self.last_textarea_name = name
450
def handle_end_textarea(self):
451
if self.skip_textarea:
452
self.skip_textarea = False
454
self.write_text('</textarea>')
455
self.in_textarea = False
456
self.skip_next = True
457
if not self.prefix_error:
458
self.write_marker(self.last_textarea_name)
459
self.last_textarea_name = None
461
def handle_select(self, attrs):
462
name = self.get_attr(attrs, 'name', False)
463
if name and self.prefix_error:
464
self.write_marker(name)
466
and self.errors.get(name)):
467
self.add_class(attrs, self.error_class)
468
self.in_select = self.get_attr(attrs, 'name', False)
469
self.write_tag('select', attrs)
470
self.skip_next = True
471
self.add_key(self.in_select)
473
def handle_end_select(self):
474
self.write_text('</select>')
475
self.skip_next = True
476
if not self.prefix_error and self.in_select:
477
self.write_marker(self.in_select)
478
self.in_select = None
480
def handle_option(self, attrs):
481
assert self.in_select is not None, (
482
"<option> outside of <select>: line %i, column %i"
484
if self.in_select != False:
485
default = self.defaults.get(self.in_select, '')
486
if self.force_defaults or (self.in_select in self.defaults):
487
if self.selected_multiple(self.defaults.get(self.in_select, ''),
488
self.get_attr(attrs, 'value', '')):
489
self.set_attr(attrs, 'selected', 'selected')
490
self.add_key(self.in_select)
492
self.del_attr(attrs, 'selected')
493
self.write_tag('option', attrs)
494
self.skip_next = True
496
def selected_multiple(self, obj, value):
498
Returns true/false if obj indicates that value should be
499
selected. If obj has a __contains__ method it is used, otherwise
504
if isinstance(obj, (str, unicode)):
506
if hasattr(obj, '__contains__'):
509
if hasattr(obj, '__iter__'):
511
if self.str_compare(inner, value):
513
return self.str_compare(obj, value)
515
def write_marker(self, marker):
516
self._content.append((marker,))
518
def insert_at_marker(self, marker, text):
519
for i, item in enumerate(self._content):
520
if item == (marker,):
521
self._content.insert(i, text)
524
self._content.insert(0, text)
526
# This can potentially be extended globally
527
default_formatter_dict = {'default': default_formatter,
528
'none': none_formatter,
529
'escape': escape_formatter,
530
'escapenl': escapenl_formatter,
531
'ignore': ignore_formatter,