Path: blob/master/venv/Lib/site-packages/lxml/html/formfill.py
811 views
from lxml.etree import XPath, ElementBase1from lxml.html import fromstring, XHTML_NAMESPACE2from lxml.html import _forms_xpath, _options_xpath, _nons, _transform_result3from lxml.html import defs4import copy56try:7basestring8except NameError:9# Python 310basestring = str1112__all__ = ['FormNotFound', 'fill_form', 'fill_form_html',13'insert_errors', 'insert_errors_html',14'DefaultErrorCreator']1516class FormNotFound(LookupError):17"""18Raised when no form can be found19"""2021_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')]),23namespaces={'x':XHTML_NAMESPACE})24_label_for_xpath = XPath('//label[@for=$for_id]|//x:label[@for=$for_id]',25namespaces={'x':XHTML_NAMESPACE})26_name_xpath = XPath('descendant-or-self::*[@name=$name]')2728def fill_form(29el,30values,31form_id=None,32form_index=None,33):34el = _find_form(el, form_id=form_id, form_index=form_index)35_fill_form(el, values)3637def fill_form_html(html, values, form_id=None, form_index=None):38result_type = type(html)39if isinstance(html, basestring):40doc = fromstring(html)41else:42doc = copy.deepcopy(html)43fill_form(doc, values, form_id=form_id, form_index=form_index)44return _transform_result(result_type, doc)4546def _fill_form(el, values):47counts = {}48if hasattr(values, 'mixed'):49# For Paste request parameters50values = values.mixed()51inputs = _input_xpath(el)52for input in inputs:53name = input.get('name')54if not name:55continue56if _takes_multiple(input):57value = values.get(name, [])58if not isinstance(value, (list, tuple)):59value = [value]60_fill_multiple(input, value)61elif name not in values:62continue63else:64index = counts.get(name, 0)65counts[name] = index + 166value = values[name]67if isinstance(value, (list, tuple)):68try:69value = value[index]70except IndexError:71continue72elif index > 0:73continue74_fill_single(input, value)7576def _takes_multiple(input):77if _nons(input.tag) == 'select' and input.get('multiple'):78# FIXME: multiple="0"?79return True80type = input.get('type', '').lower()81if type in ('radio', 'checkbox'):82return True83return False8485def _fill_multiple(input, value):86type = input.get('type', '').lower()87if type == 'checkbox':88v = input.get('value')89if v is None:90if not value:91result = False92else:93result = value[0]94if isinstance(value, basestring):95# The only valid "on" value for an unnamed checkbox is 'on'96result = result == 'on'97_check(input, result)98else:99_check(input, v in value)100elif type == 'radio':101v = input.get('value')102_check(input, v in value)103else:104assert _nons(input.tag) == 'select'105for option in _options_xpath(input):106v = option.get('value')107if v is None:108# This seems to be the default, at least on IE109# FIXME: but I'm not sure110v = option.text_content()111_select(option, v in value)112113def _check(el, check):114if check:115el.set('checked', '')116else:117if 'checked' in el.attrib:118del el.attrib['checked']119120def _select(el, select):121if select:122el.set('selected', '')123else:124if 'selected' in el.attrib:125del el.attrib['selected']126127def _fill_single(input, value):128if _nons(input.tag) == 'textarea':129input.text = value130else:131input.set('value', value)132133def _find_form(el, form_id=None, form_index=None):134if form_id is None and form_index is None:135forms = _forms_xpath(el)136for form in forms:137return form138raise FormNotFound(139"No forms in page")140if form_id is not None:141form = el.get_element_by_id(form_id)142if form is not None:143return form144forms = _form_name_xpath(el, name=form_id)145if forms:146return forms[0]147else:148raise FormNotFound(149"No form with the name or id of %r (forms: %s)"150% (id, ', '.join(_find_form_ids(el))))151if form_index is not None:152forms = _forms_xpath(el)153try:154return forms[form_index]155except IndexError:156raise FormNotFound(157"There is no form with the index %r (%i forms found)"158% (form_index, len(forms)))159160def _find_form_ids(el):161forms = _forms_xpath(el)162if not forms:163yield '(no forms)'164return165for index, form in enumerate(forms):166if form.get('id'):167if form.get('name'):168yield '%s or %s' % (form.get('id'),169form.get('name'))170else:171yield form.get('id')172elif form.get('name'):173yield form.get('name')174else:175yield '(unnamed form %s)' % index176177############################################################178## Error filling179############################################################180181class DefaultErrorCreator(object):182insert_before = True183block_inside = True184error_container_tag = 'div'185error_message_class = 'error-message'186error_block_class = 'error-block'187default_message = "Invalid"188189def __init__(self, **kw):190for name, value in kw.items():191if not hasattr(self, name):192raise TypeError(193"Unexpected keyword argument: %s" % name)194setattr(self, name, value)195196def __call__(self, el, is_block, message):197error_el = el.makeelement(self.error_container_tag)198if self.error_message_class:199error_el.set('class', self.error_message_class)200if is_block and self.error_block_class:201error_el.set('class', error_el.get('class', '')+' '+self.error_block_class)202if message is None or message == '':203message = self.default_message204if isinstance(message, ElementBase):205error_el.append(message)206else:207assert isinstance(message, basestring), (208"Bad message; should be a string or element: %r" % message)209error_el.text = message or self.default_message210if is_block and self.block_inside:211if self.insert_before:212error_el.tail = el.text213el.text = None214el.insert(0, error_el)215else:216el.append(error_el)217else:218parent = el.getparent()219pos = parent.index(el)220if self.insert_before:221parent.insert(pos, error_el)222else:223error_el.tail = el.tail224el.tail = None225parent.insert(pos+1, error_el)226227default_error_creator = DefaultErrorCreator()228229230def insert_errors(231el,232errors,233form_id=None,234form_index=None,235error_class="error",236error_creator=default_error_creator,237):238el = _find_form(el, form_id=form_id, form_index=form_index)239for name, error in errors.items():240if error is None:241continue242for error_el, message in _find_elements_for_name(el, name, error):243assert isinstance(message, (basestring, type(None), ElementBase)), (244"Bad message: %r" % message)245_insert_error(error_el, message, error_class, error_creator)246247def insert_errors_html(html, values, **kw):248result_type = type(html)249if isinstance(html, basestring):250doc = fromstring(html)251else:252doc = copy.deepcopy(html)253insert_errors(doc, values, **kw)254return _transform_result(result_type, doc)255256def _insert_error(el, error, error_class, error_creator):257if _nons(el.tag) in defs.empty_tags or _nons(el.tag) == 'textarea':258is_block = False259else:260is_block = True261if _nons(el.tag) != 'form' and error_class:262_add_class(el, error_class)263if el.get('id'):264labels = _label_for_xpath(el, for_id=el.get('id'))265if labels:266for label in labels:267_add_class(label, error_class)268error_creator(el, is_block, error)269270def _add_class(el, class_name):271if el.get('class'):272el.set('class', el.get('class')+' '+class_name)273else:274el.set('class', class_name)275276def _find_elements_for_name(form, name, error):277if name is None:278# An error for the entire form279yield form, error280return281if name.startswith('#'):282# By id283el = form.get_element_by_id(name[1:])284if el is not None:285yield el, error286return287els = _name_xpath(form, name=name)288if not els:289# FIXME: should this raise an exception?290return291if not isinstance(error, (list, tuple)):292yield els[0], error293return294# FIXME: if error is longer than els, should it raise an error?295for el, err in zip(els, error):296if err is None:297continue298yield el, err299300301