Package lxml :: Package html :: Module formfill
[hide private]
[frames] | no frames]

Source Code for Module lxml.html.formfill

  1  from lxml.etree import XPath, ElementBase 
  2  from lxml.html import fromstring, tostring, XHTML_NAMESPACE 
  3  from lxml.html import _forms_xpath, _options_xpath, _nons, _transform_result 
  4  from lxml.html import defs 
  5  import copy 
  6  try: 
  7      basestring = __builtins__["basestring"] 
  8  except (KeyError, NameError): 
  9      # Python 3 
 10      basestring = str 
 11   
 12  __all__ = ['FormNotFound', 'fill_form', 'fill_form_html', 
 13             'insert_errors', 'insert_errors_html', 
 14             'DefaultErrorCreator'] 
 15   
16 -class FormNotFound(LookupError):
17 """ 18 Raised when no form can be found 19 """
20 21 _form_name_xpath = XPath('descendant-or-self::form[name=$name]|descendant-or-self::x:form[name=$name]', namespaces={'x':XHTML_NAMESPACE}) 22 _input_xpath = XPath('|'.join(['descendant-or-self::'+_tag for _tag in ('input','select','textarea','x:input','x:select','x:textarea')]), 23 namespaces={'x':XHTML_NAMESPACE}) 24 _label_for_xpath = XPath('//label[@for=$for_id]|//x:label[@for=$for_id]', 25 namespaces={'x':XHTML_NAMESPACE}) 26 _name_xpath = XPath('descendant-or-self::*[@name=$name]') 27
28 -def fill_form( 29 el, 30 values, 31 form_id=None, 32 form_index=None, 33 ):
34 el = _find_form(el, form_id=form_id, form_index=form_index) 35 _fill_form(el, values)
36
37 -def fill_form_html(html, values, form_id=None, form_index=None):
38 result_type = type(html) 39 if isinstance(html, basestring): 40 doc = fromstring(html) 41 else: 42 doc = copy.deepcopy(html) 43 fill_form(doc, values, form_id=form_id, form_index=form_index) 44 return _transform_result(result_type, doc)
45
46 -def _fill_form(el, values):
47 counts = {} 48 if hasattr(values, 'mixed'): 49 # For Paste request parameters 50 values = values.mixed() 51 inputs = _input_xpath(el) 52 for input in inputs: 53 name = input.get('name') 54 if not name: 55 continue 56 if _takes_multiple(input): 57 value = values.get(name, []) 58 if not isinstance(value, (list, tuple)): 59 value = [value] 60 _fill_multiple(input, value) 61 elif name not in values: 62 continue 63 else: 64 index = counts.get(name, 0) 65 counts[name] = index + 1 66 value = values[name] 67 if isinstance(value, (list, tuple)): 68 try: 69 value = value[index] 70 except IndexError: 71 continue 72 elif index > 0: 73 continue 74 _fill_single(input, value)
75
76 -def _takes_multiple(input):
77 if _nons(input.tag) == 'select' and input.get('multiple'): 78 # FIXME: multiple="0"? 79 return True 80 type = input.get('type', '').lower() 81 if type in ('radio', 'checkbox'): 82 return True 83 return False
84
85 -def _fill_multiple(input, value):
86 type = input.get('type', '').lower() 87 if type == 'checkbox': 88 v = input.get('value') 89 if v is None: 90 if not value: 91 result = False 92 else: 93 result = value[0] 94 if isinstance(value, basestring): 95 # The only valid "on" value for an unnamed checkbox is 'on' 96 result = result == 'on' 97 _check(input, result) 98 else: 99 _check(input, v in value) 100 elif type == 'radio': 101 v = input.get('value') 102 _check(input, v in value) 103 else: 104 assert _nons(input.tag) == 'select' 105 for option in _options_xpath(input): 106 v = option.get('value') 107 if v is None: 108 # This seems to be the default, at least on IE 109 # FIXME: but I'm not sure 110 v = option.text_content() 111 _select(option, v in value)
112
113 -def _check(el, check):
114 if check: 115 el.set('checked', '') 116 else: 117 if 'checked' in el.attrib: 118 del el.attrib['checked']
119
120 -def _select(el, select):
121 if select: 122 el.set('selected', '') 123 else: 124 if 'selected' in el.attrib: 125 del el.attrib['selected']
126
127 -def _fill_single(input, value):
128 if _nons(input.tag) == 'textarea': 129 input.clear() 130 input.text = value 131 else: 132 input.set('value', value)
133
134 -def _find_form(el, form_id=None, form_index=None):
135 if form_id is None and form_index is None: 136 forms = _forms_xpath(el) 137 for form in forms: 138 return form 139 raise FormNotFound( 140 "No forms in page") 141 if form_id is not None: 142 form = el.get_element_by_id(form_id) 143 if form is not None: 144 return form 145 forms = _form_name_xpath(el, name=form_id) 146 if forms: 147 return forms[0] 148 else: 149 raise FormNotFound( 150 "No form with the name or id of %r (forms: %s)" 151 % (id, ', '.join(_find_form_ids(el)))) 152 if form_index is not None: 153 forms = _forms_xpath(el) 154 try: 155 return forms[form_index] 156 except IndexError: 157 raise FormNotFound( 158 "There is no form with the index %r (%i forms found)" 159 % (form_index, len(forms)))
160
161 -def _find_form_ids(el):
162 forms = _forms_xpath(el) 163 if not forms: 164 yield '(no forms)' 165 return 166 for index, form in enumerate(forms): 167 if form.get('id'): 168 if form.get('name'): 169 yield '%s or %s' % (form.get('id'), 170 form.get('name')) 171 else: 172 yield form.get('id') 173 elif form.get('name'): 174 yield form.get('name') 175 else: 176 yield '(unnamed form %s)' % index
177 178 ############################################################ 179 ## Error filling 180 ############################################################ 181
182 -class DefaultErrorCreator(object):
183 insert_before = True 184 block_inside = True 185 error_container_tag = 'div' 186 error_message_class = 'error-message' 187 error_block_class = 'error-block' 188 default_message = "Invalid" 189
190 - def __init__(self, **kw):
191 for name, value in kw.items(): 192 if not hasattr(self, name): 193 raise TypeError( 194 "Unexpected keyword argument: %s" % name) 195 setattr(self, name, value)
196
197 - def __call__(self, el, is_block, message):
198 error_el = el.makeelement(self.error_container_tag) 199 if self.error_message_class: 200 error_el.set('class', self.error_message_class) 201 if is_block and self.error_block_class: 202 error_el.set('class', error_el.get('class', '')+' '+self.error_block_class) 203 if message is None or message == '': 204 message = self.default_message 205 if isinstance(message, ElementBase): 206 error_el.append(message) 207 else: 208 assert isinstance(message, basestring), ( 209 "Bad message; should be a string or element: %r" % message) 210 error_el.text = message or self.default_message 211 if is_block and self.block_inside: 212 if self.insert_before: 213 error_el.tail = el.text 214 el.text = None 215 el.insert(0, error_el) 216 else: 217 el.append(error_el) 218 else: 219 parent = el.getparent() 220 pos = parent.index(el) 221 if self.insert_before: 222 parent.insert(pos, error_el) 223 else: 224 error_el.tail = el.tail 225 el.tail = None 226 parent.insert(pos+1, error_el)
227 228 default_error_creator = DefaultErrorCreator() 229 230
231 -def insert_errors( 232 el, 233 errors, 234 form_id=None, 235 form_index=None, 236 error_class="error", 237 error_creator=default_error_creator, 238 ):
239 el = _find_form(el, form_id=form_id, form_index=form_index) 240 for name, error in errors.items(): 241 if error is None: 242 continue 243 for error_el, message in _find_elements_for_name(el, name, error): 244 assert isinstance(message, (basestring, type(None), ElementBase)), ( 245 "Bad message: %r" % message) 246 _insert_error(error_el, message, error_class, error_creator)
247
248 -def insert_errors_html(html, values, **kw):
249 result_type = type(html) 250 if isinstance(html, basestring): 251 doc = fromstring(html) 252 else: 253 doc = copy.deepcopy(html) 254 insert_errors(doc, values, **kw) 255 return _transform_result(result_type, doc)
256
257 -def _insert_error(el, error, error_class, error_creator):
258 if _nons(el.tag) in defs.empty_tags or _nons(el.tag) == 'textarea': 259 is_block = False 260 else: 261 is_block = True 262 if _nons(el.tag) != 'form' and error_class: 263 _add_class(el, error_class) 264 if el.get('id'): 265 labels = _label_for_xpath(el, for_id=el.get('id')) 266 if labels: 267 for label in labels: 268 _add_class(label, error_class) 269 error_creator(el, is_block, error)
270
271 -def _add_class(el, class_name):
272 if el.get('class'): 273 el.set('class', el.get('class')+' '+class_name) 274 else: 275 el.set('class', class_name)
276
277 -def _find_elements_for_name(form, name, error):
278 if name is None: 279 # An error for the entire form 280 yield form, error 281 return 282 if name.startswith('#'): 283 # By id 284 el = form.get_element_by_id(name[1:]) 285 if el is not None: 286 yield el, error 287 return 288 els = _name_xpath(form, name=name) 289 if not els: 290 # FIXME: should this raise an exception? 291 return 292 if not isinstance(error, (list, tuple)): 293 yield els[0], error 294 return 295 # FIXME: if error is longer than els, should it raise an error? 296 for el, err in zip(els, error): 297 if err is None: 298 continue 299 yield el, err
300