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

Source Code for Module lxml.html.formfill

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