0001"""
0002Extension to ``htmlfill`` that can parse out schema-defining
0003statements.
0004
0005You can either pass ``SchemaBuilder`` to ``htmlfill.render`` (the
0006``listen`` argument), or call ``parse_schema`` to just parse out a
0007``Schema`` object.
0008"""
0009
0010import validators, schema, compound
0011
0012__all__ = ['parse_schema', 'SchemaBuilder']
0013
0014def parse_schema(form):
0015 """
0016 Given an HTML form, parse out the schema defined in it and return
0017 that schema.
0018 """
0019 listener = htmlfill_schemabuilder.SchemaBuilder()
0020 p = htmlfill.FillingParser(
0021 defaults={}, listener=listener)
0022 p.feed(self.form)
0023 p.close()
0024 return listener.schema()
0025
0026default_validators = dict(
0027 [(name.lower(), getattr(validators, name))
0028 for name in dir(validators)])
0029
0030def get_messages(cls, message):
0031 if not message:
0032 return {}
0033 else:
0034 return dict([(k, message) for k in cls._messages.keys()])
0035
0036def to_bool(value):
0037 value = value.strip().lower()
0038 if value in ('true', 't', 'yes', 'y', 'on', '1'):
0039 return True
0040 elif value in ('false', 'f', 'no', 'n', 'off', '0'):
0041 return False
0042 else:
0043 raise ValueError("Not a boolean value: %r (use 'true'/'false')")
0044
0045def force_list(v):
0046 """
0047 Force single items into a list. This is useful for checkboxes.
0048 """
0049 if isinstance(v, list):
0050 return v
0051 elif isinstance(v, tuple):
0052 return list(v)
0053 else:
0054 return [v]
0055
0056class SchemaBuilder(object):
0057
0058 def __init__(self, validators=default_validators):
0059 self.validators = validators
0060 self._schema = None
0061
0062 def reset(self):
0063 self._schema = schema.Schema()
0064
0065 def schema(self):
0066 return self._schema
0067
0068 def listen_input(self, parser, tag, attrs):
0069 get_attr = parser.get_attr
0070 name = get_attr(attrs, 'name')
0071 if not name:
0072
0073 return
0074 v = compound.All(validators.Identity())
0075 add_to_end = None
0076
0077 if tag.lower() == "input":
0078 type_attr = get_attr(attrs, "type").lower().strip()
0079 if type_attr == "submit":
0080 v.validators.append(validators.Bool())
0081 elif type_attr == "checkbox":
0082 v.validators.append(validators.Wrapper(to_python = force_list))
0083 elif type_attr == "file":
0084 add_to_end = validators.FieldStorageUploadConverter()
0085 message = get_attr(attrs, 'form:message')
0086 required = to_bool(get_attr(attrs, 'form:required', 'false'))
0087 if required:
0088 v.validators.append(
0089 validators.NotEmpty(
0090 messages=get_messages(validators.NotEmpty, message)))
0091 else:
0092 v.validators[0].if_missing = False
0093 if add_to_end:
0094 v.validators.append(add_to_end)
0095 v_type = get_attr(attrs, 'form:validate', None)
0096 if v_type:
0097 pos = v_type.find(':')
0098 if pos != -1:
0099
0100 args = (v_type[pos+1:],)
0101 v_type = v_type[:pos]
0102 else:
0103 args = ()
0104 v_type = v_type.lower()
0105 v_class = self.validators.get(v_type)
0106 if not v_class:
0107 raise ValueError("Invalid validation type: %r" % v_type)
0108 kw_args={'messages': get_messages(v_class, message)}
0109 v_inst = v_class(
0110 *args, **kw_args)
0111 v.validators.append(v_inst)
0112 self._schema.add_field(name, v)