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.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