From: <sub...@co...> - 2005-03-13 08:57:46
|
Author: ianb Date: 2005-03-13 08:57:42 +0000 (Sun, 13 Mar 2005) New Revision: 674 Added: trunk/Validator/validator/tests/ trunk/Validator/validator/tests/htmlfill_data/ trunk/Validator/validator/tests/htmlfill_data/data-1.txt trunk/Validator/validator/tests/htmlfill_data/data-2.txt trunk/Validator/validator/tests/htmlfill_data/data-3.txt trunk/Validator/validator/tests/htmlfill_data/data-error1.txt trunk/Validator/validator/tests/test_htmlfill.py Modified: trunk/Validator/validator/htmlfill.py Log: * Added tests for htmlfill * Fixed a bug with error class on <select> * Fixed the newline-eating bug * Added formatters "none" and "escape" * Peter Hunts changes to allow validation schemas to be defined in a form * And his fix for XHTML-style tags Modified: trunk/Validator/validator/htmlfill.py =================================================================== --- trunk/Validator/validator/htmlfill.py 2005-03-13 08:55:50 UTC (rev 673) +++ trunk/Validator/validator/htmlfill.py 2005-03-13 08:57:42 UTC (rev 674) @@ -1,9 +1,25 @@ import HTMLParser import cgi +import validators, schema, compound + def default_formatter(error): - return '<span class="error-message">%s</span><br>\n' % cgi.escape(error) + return '<span class="error-message">%s</span><br />\n' % cgi.escape(error) +def none_formatter(error): + return error + +def escape_formatter(error): + return cgi.escape(error, 1) + +default_validators = dict([(name.lower(), getattr(validators, name)) for name in dir(validators)]) + +def get_messages(cls, message): + if not message: + return {} + else: + return dict([(k, message) for k in cls._messages.keys()]) + class FillingParser(HTMLParser.HTMLParser): r""" Fills HTML with default values, as in a form. @@ -38,7 +54,7 @@ def __init__(self, defaults, errors=None, use_all_keys=False, error_formatters=None, error_class='error', - add_attributes=None): + add_attributes=None, validators=default_validators): HTMLParser.HTMLParser.__init__(self) self.content = [] self.source = None @@ -55,16 +71,21 @@ self.used_keys = {} self.used_errors = {} if error_formatters is None: - self.error_formatters = {'default': default_formatter} + self.error_formatters = {'default': default_formatter, + 'none': none_formatter, + 'escape': escape_formatter} else: self.error_formatters = error_formatters self.error_class = error_class self.add_attributes = add_attributes or {} + self.validators = validators + self._schema = None def feed(self, data): self.source = data self.lines = data.split('\n') self.source_pos = 1, 0 + self._schema = schema.Schema() HTMLParser.HTMLParser.feed(self, data) def close(self): @@ -92,20 +113,26 @@ def add_key(self, key): self.used_keys[key] = 1 - def handle_starttag(self, tag, attrs): + def handle_starttag(self, tag, attrs, startend=False): self.write_pos() if tag == 'input': - self.handle_input(attrs) + self.handle_input(attrs, startend) elif tag == 'textarea': self.handle_textarea(attrs) elif tag == 'select': self.handle_select(attrs) elif tag == 'option': self.handle_option(attrs) + return elif tag == 'form:error': self.handle_error(attrs) + return elif tag == 'form:iferror': self.handle_iferror(attrs) + return + else: + return + self.update_schema(attrs) def handle_misc(self, whatever): self.write_pos() @@ -126,6 +153,9 @@ elif tag == 'form:iferror': self.handle_end_iferror() + def handle_startendtag(self, tag, attrs): + return self.handle_starttag(tag, attrs, True) + def handle_iferror(self, attrs): name = self.get_attr(attrs, 'name') assert name, "Name attribute in <iferror> required (%s)" % self.getpos() @@ -137,7 +167,7 @@ def handle_end_iferror(self): self.in_error = None self.skip_error = False - self.skip_next = False + self.skip_next = True def handle_error(self, attrs): name = self.get_attr(attrs, 'name') @@ -154,7 +184,7 @@ self.skip_next = True self.used_errors[name] = 1 - def handle_input(self, attrs): + def handle_input(self, attrs, startend): t = (self.get_attr(attrs, 'type') or 'text').lower() name = self.get_attr(attrs, 'name') value = self.defaults.get(name) @@ -173,7 +203,7 @@ if t in ('text', 'hidden', 'submit', 'reset', 'button'): self.set_attr(attrs, 'value', value or self.get_attr(attrs, 'value', '')) - self.write_tag('input', attrs) + self.write_tag('input', attrs, startend) self.skip_next = True self.add_key(name) elif t == 'checkbox': @@ -209,11 +239,11 @@ % (t, self.getpos()) def handle_textarea(self, attrs): + name = self.get_attr(attrs, 'name') if (self.error_class - and self.errors.get(self.get_attr(attrs, 'name'))): + and self.errors.get(name)): self.add_class(attrs, self.error_class) self.write_tag('textarea', attrs) - name = self.get_attr(attrs, 'name') value = self.defaults.get(name, '') self.write_text(cgi.escape(value, 1)) self.write_text('</textarea>') @@ -225,11 +255,15 @@ self.skip_next = True def handle_select(self, attrs): + name = self.get_attr(attrs, 'name') if (self.error_class - and self.errors.get(self.get_attr(attrs, 'name'))): + and self.errors.get(name)): self.add_class(attrs, self.error_class) self.in_select = self.get_attr(attrs, 'name') + self.write_tag('select', attrs) + self.skip_next = True self.add_key(self.in_select) + def handle_end_select(self): self.in_select = None @@ -245,12 +279,49 @@ self.write_tag('option', attrs) self.skip_next = True + def update_schema(self, attrs): + name = self.get_attr(attrs, "name") + if not name: + return + v = compound.All() + message = self.get_attr(attrs, "form:message") + required = self.get_attr(attrs, "form:required", "no").lower() + required = (required == "yes") or (required == "true") + if required: + v.validators.append(validators.NotEmpty(messages=get_messages(validators.NotEmpty, message))) + t = self.get_attr(attrs, "form:validate", None) + if t: + # validatorname[:argument] + i = t.find(":") + if i > -1: + args = t[i+1:] # TODO: split on commas? + t = t[:i].lower() + else: + args = None + t = t.lower() + # get the validator class by name + vclass = self.validators.get(t) + if not vclass: + raise ValueError, "Invalid validation type: " + t + if args: + vinst = vclass(args, messages=get_messages(vclass, message)) # TODO: if something takes more than 1 string argument, wrap in a function which parses args + else: + vinst = vclass(messages=get_messages(vclass, message)) + v.validators.append(vinst) + self._schema.add_field(name, v) + + def schema(self): + return self._schema + def write_text(self, text): self.content.append(text) - def write_tag(self, tag, attrs): + def write_tag(self, tag, attrs, startend=False): attr_text = ''.join([' %s="%s"' % (n, cgi.escape(str(v), 1)) - for (n, v) in attrs]) + for (n, v) in attrs + if not n.startswith('form:')]) + if startend: + attr_text += " /" self.write_text('<%s%s>' % (tag, attr_text)) def write_pos(self): @@ -268,6 +339,7 @@ else: self.write_text( self.lines[self.source_pos[0]-1][self.source_pos[1]:]) + self.write_text('\n') for i in range(self.source_pos[0]+1, cur_line): self.write_text(self.lines[i-1]) self.write_text('\n') Added: trunk/Validator/validator/tests/htmlfill_data/data-1.txt =================================================================== --- trunk/Validator/validator/tests/htmlfill_data/data-1.txt 2005-03-13 08:55:50 UTC (rev 673) +++ trunk/Validator/validator/tests/htmlfill_data/data-1.txt 2005-03-13 08:57:42 UTC (rev 674) @@ -0,0 +1,7 @@ +<test tag> +<html><body> +<blah blah="2"> +---- +<test tag> +<html><body> +<blah blah="2"> Added: trunk/Validator/validator/tests/htmlfill_data/data-2.txt =================================================================== --- trunk/Validator/validator/tests/htmlfill_data/data-2.txt 2005-03-13 08:55:50 UTC (rev 673) +++ trunk/Validator/validator/tests/htmlfill_data/data-2.txt 2005-03-13 08:57:42 UTC (rev 674) @@ -0,0 +1,15 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"> +<html> +<form action=""> +<input type="text" name="test"> +</form> +</html> +---- +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"> +<html> +<form action=""> +<input type="text" name="test" value="whatever"> +</form> +</html> +---- +defaults={'test': 'whatever'} Added: trunk/Validator/validator/tests/htmlfill_data/data-3.txt =================================================================== --- trunk/Validator/validator/tests/htmlfill_data/data-3.txt 2005-03-13 08:55:50 UTC (rev 673) +++ trunk/Validator/validator/tests/htmlfill_data/data-3.txt 2005-03-13 08:57:42 UTC (rev 674) @@ -0,0 +1,44 @@ +<form> +<select name="sel1"> +<option value="1">1 +<option value="2">2 +</select> +<textarea name="test2">this is a test</textarea> +<input type="checkbox" name="check1" checked> +<input type="checkbox" name="check2" value="cc" checked> +<input type="radio" name="radio1" value="a"> +<input type="radio" name="radio1" value="b"> +<input type="radio" name="radio1" value="c"> +<input type="hidden" name="hidden1"> +<input type="text" style="width: 100%" name="text1" value="X"> +<input type="submit" name="submit1"> +<input type="reset" name="reset1"> +</form> +---- +<form> +<select name="sel1"> +<option value="1">1 +<option value="2" selected="selected">2 +</select> +<textarea name="test2">TEXTAREA</textarea> +<input type="checkbox" name="check1" checked="checked"> +<input type="checkbox" name="check2" value="cc"> +<input type="radio" name="radio1" value="a"> +<input type="radio" name="radio1" value="b" checked="checked"> +<input type="radio" name="radio1" value="c"> +<input type="hidden" name="hidden1" value="H"> +<input type="text" style="width: 100%" name="text1" value="T"> +<input type="submit" name="submit1" value="SAVE"> +<input type="reset" name="reset1" value="CANCEL"> +</form> +---- +defaults = dict( + sel1='2', + test2='TEXTAREA', + check1=True, + check2=False, + radio1='b', + hidden1='H', + text1='T', + submit1='SAVE', + reset1='CANCEL') \ No newline at end of file Added: trunk/Validator/validator/tests/htmlfill_data/data-error1.txt =================================================================== --- trunk/Validator/validator/tests/htmlfill_data/data-error1.txt 2005-03-13 08:55:50 UTC (rev 673) +++ trunk/Validator/validator/tests/htmlfill_data/data-error1.txt 2005-03-13 08:57:42 UTC (rev 674) @@ -0,0 +1,47 @@ +<form> +<form:error name="" format="none"> + +<form:iferror name="t1"> +!!!<form:error format="escape">!!! +</form:iferror> +<input type="text" name="t1"> + +<form:error name="t2"> +<select name="t2"> +<option value="1">1</option> +<option value="2">2</option> +</select> + +<form:error name="t3"> +<textarea name="t3">hey</textarea> + +<form:iferror name="X"> +This should not display +</form:iferror> + +---- +<form> +<test!> + + +!!!<HEY>!!! + +<input type="text" name="t1" class="error" value=""> + +<span class="error-message"><error</span><br /> + +<select name="t2" class="error"> +<option value="1">1</option> +<option value="2">2</option> +</select> + +<span class="error-message">last</span><br /> + +<textarea name="t3" class="error"></textarea> +---- +defaults = {} +errors = dict( + t1='<HEY>', + t2='<error', + t3='last') +errors[''] = '<test!>' Added: trunk/Validator/validator/tests/test_htmlfill.py =================================================================== --- trunk/Validator/validator/tests/test_htmlfill.py 2005-03-13 08:55:50 UTC (rev 673) +++ trunk/Validator/validator/tests/test_htmlfill.py 2005-03-13 08:57:42 UTC (rev 674) @@ -0,0 +1,60 @@ +import sys +import os +import re + +base_dir = os.path.dirname(os.path.dirname(os.path.dirname( + os.path.abspath(__file__)))) +if base_dir not in sys.path: + sys.path.insert(0, base_dir) +from validator import htmlfill +from validator.doctest_xml_compare import xml_compare +from elementtree import ElementTree as et +from xml.parsers.expat import ExpatError + +def test_inputoutput(): + data_dir = os.path.join(os.path.dirname(__file__), 'htmlfill_data') + for fn in os.listdir(data_dir): + if fn.startswith('data-'): + fn = os.path.join(data_dir, fn) + yield run_filename, fn + +def run_filename(filename): + f = open(filename) + content = f.read() + f.close() + parts = re.split(r'---*', content) + template = parts[0] + expected = parts[1] + if len(parts) == 3: + data_content = parts[2].strip() + elif len(parts) > 3: + print parts[3:] + assert 0, "Too many sections" + else: + data_content = '' + data = {} + if data_content: + exec data_content in data + data['defaults'] = data.get('defaults', {}) + for name in data.keys(): + if name.startswith('_') or hasattr(__builtins__, name): + del data[name] + p = htmlfill.FillingParser(**data) + p.feed(template) + p.close() + output = p.text() + def reporter(v): + print v + try: + output_xml = et.XML(output) + expected_xml = et.XML(expected) + except ExpatError: + comp = output.strip() == expected.strip() + else: + comp = xml_compare(output_xml, expected_xml, reporter) + if not comp: + print '---- Output: ----' + print output + print '---- Expected: ----' + print expected + assert 0 |