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