|
|
- from lxml.etree import XPath, ElementBase
- from lxml.html import fromstring, XHTML_NAMESPACE
- from lxml.html import _forms_xpath, _options_xpath, _nons, _transform_result
- from lxml.html import defs
- import copy
-
- try:
- basestring
- except NameError:
- # Python 3
- basestring = str
-
- __all__ = ['FormNotFound', 'fill_form', 'fill_form_html',
- 'insert_errors', 'insert_errors_html',
- 'DefaultErrorCreator']
-
- class FormNotFound(LookupError):
- """
- Raised when no form can be found
- """
-
- _form_name_xpath = XPath('descendant-or-self::form[name=$name]|descendant-or-self::x:form[name=$name]', namespaces={'x':XHTML_NAMESPACE})
- _input_xpath = XPath('|'.join(['descendant-or-self::'+_tag for _tag in ('input','select','textarea','x:input','x:select','x:textarea')]),
- namespaces={'x':XHTML_NAMESPACE})
- _label_for_xpath = XPath('//label[@for=$for_id]|//x:label[@for=$for_id]',
- namespaces={'x':XHTML_NAMESPACE})
- _name_xpath = XPath('descendant-or-self::*[@name=$name]')
-
- def fill_form(
- el,
- values,
- form_id=None,
- form_index=None,
- ):
- el = _find_form(el, form_id=form_id, form_index=form_index)
- _fill_form(el, values)
-
- def fill_form_html(html, values, form_id=None, form_index=None):
- result_type = type(html)
- if isinstance(html, basestring):
- doc = fromstring(html)
- else:
- doc = copy.deepcopy(html)
- fill_form(doc, values, form_id=form_id, form_index=form_index)
- return _transform_result(result_type, doc)
-
- def _fill_form(el, values):
- counts = {}
- if hasattr(values, 'mixed'):
- # For Paste request parameters
- values = values.mixed()
- inputs = _input_xpath(el)
- for input in inputs:
- name = input.get('name')
- if not name:
- continue
- if _takes_multiple(input):
- value = values.get(name, [])
- if not isinstance(value, (list, tuple)):
- value = [value]
- _fill_multiple(input, value)
- elif name not in values:
- continue
- else:
- index = counts.get(name, 0)
- counts[name] = index + 1
- value = values[name]
- if isinstance(value, (list, tuple)):
- try:
- value = value[index]
- except IndexError:
- continue
- elif index > 0:
- continue
- _fill_single(input, value)
-
- def _takes_multiple(input):
- if _nons(input.tag) == 'select' and input.get('multiple'):
- # FIXME: multiple="0"?
- return True
- type = input.get('type', '').lower()
- if type in ('radio', 'checkbox'):
- return True
- return False
-
- def _fill_multiple(input, value):
- type = input.get('type', '').lower()
- if type == 'checkbox':
- v = input.get('value')
- if v is None:
- if not value:
- result = False
- else:
- result = value[0]
- if isinstance(value, basestring):
- # The only valid "on" value for an unnamed checkbox is 'on'
- result = result == 'on'
- _check(input, result)
- else:
- _check(input, v in value)
- elif type == 'radio':
- v = input.get('value')
- _check(input, v in value)
- else:
- assert _nons(input.tag) == 'select'
- for option in _options_xpath(input):
- v = option.get('value')
- if v is None:
- # This seems to be the default, at least on IE
- # FIXME: but I'm not sure
- v = option.text_content()
- _select(option, v in value)
-
- def _check(el, check):
- if check:
- el.set('checked', '')
- else:
- if 'checked' in el.attrib:
- del el.attrib['checked']
-
- def _select(el, select):
- if select:
- el.set('selected', '')
- else:
- if 'selected' in el.attrib:
- del el.attrib['selected']
-
- def _fill_single(input, value):
- if _nons(input.tag) == 'textarea':
- input.text = value
- else:
- input.set('value', value)
-
- def _find_form(el, form_id=None, form_index=None):
- if form_id is None and form_index is None:
- forms = _forms_xpath(el)
- for form in forms:
- return form
- raise FormNotFound(
- "No forms in page")
- if form_id is not None:
- form = el.get_element_by_id(form_id)
- if form is not None:
- return form
- forms = _form_name_xpath(el, name=form_id)
- if forms:
- return forms[0]
- else:
- raise FormNotFound(
- "No form with the name or id of %r (forms: %s)"
- % (id, ', '.join(_find_form_ids(el))))
- if form_index is not None:
- forms = _forms_xpath(el)
- try:
- return forms[form_index]
- except IndexError:
- raise FormNotFound(
- "There is no form with the index %r (%i forms found)"
- % (form_index, len(forms)))
-
- def _find_form_ids(el):
- forms = _forms_xpath(el)
- if not forms:
- yield '(no forms)'
- return
- for index, form in enumerate(forms):
- if form.get('id'):
- if form.get('name'):
- yield '%s or %s' % (form.get('id'),
- form.get('name'))
- else:
- yield form.get('id')
- elif form.get('name'):
- yield form.get('name')
- else:
- yield '(unnamed form %s)' % index
-
- ############################################################
- ## Error filling
- ############################################################
-
- class DefaultErrorCreator(object):
- insert_before = True
- block_inside = True
- error_container_tag = 'div'
- error_message_class = 'error-message'
- error_block_class = 'error-block'
- default_message = "Invalid"
-
- def __init__(self, **kw):
- for name, value in kw.items():
- if not hasattr(self, name):
- raise TypeError(
- "Unexpected keyword argument: %s" % name)
- setattr(self, name, value)
-
- def __call__(self, el, is_block, message):
- error_el = el.makeelement(self.error_container_tag)
- if self.error_message_class:
- error_el.set('class', self.error_message_class)
- if is_block and self.error_block_class:
- error_el.set('class', error_el.get('class', '')+' '+self.error_block_class)
- if message is None or message == '':
- message = self.default_message
- if isinstance(message, ElementBase):
- error_el.append(message)
- else:
- assert isinstance(message, basestring), (
- "Bad message; should be a string or element: %r" % message)
- error_el.text = message or self.default_message
- if is_block and self.block_inside:
- if self.insert_before:
- error_el.tail = el.text
- el.text = None
- el.insert(0, error_el)
- else:
- el.append(error_el)
- else:
- parent = el.getparent()
- pos = parent.index(el)
- if self.insert_before:
- parent.insert(pos, error_el)
- else:
- error_el.tail = el.tail
- el.tail = None
- parent.insert(pos+1, error_el)
-
- default_error_creator = DefaultErrorCreator()
-
-
- def insert_errors(
- el,
- errors,
- form_id=None,
- form_index=None,
- error_class="error",
- error_creator=default_error_creator,
- ):
- el = _find_form(el, form_id=form_id, form_index=form_index)
- for name, error in errors.items():
- if error is None:
- continue
- for error_el, message in _find_elements_for_name(el, name, error):
- assert isinstance(message, (basestring, type(None), ElementBase)), (
- "Bad message: %r" % message)
- _insert_error(error_el, message, error_class, error_creator)
-
- def insert_errors_html(html, values, **kw):
- result_type = type(html)
- if isinstance(html, basestring):
- doc = fromstring(html)
- else:
- doc = copy.deepcopy(html)
- insert_errors(doc, values, **kw)
- return _transform_result(result_type, doc)
-
- def _insert_error(el, error, error_class, error_creator):
- if _nons(el.tag) in defs.empty_tags or _nons(el.tag) == 'textarea':
- is_block = False
- else:
- is_block = True
- if _nons(el.tag) != 'form' and error_class:
- _add_class(el, error_class)
- if el.get('id'):
- labels = _label_for_xpath(el, for_id=el.get('id'))
- if labels:
- for label in labels:
- _add_class(label, error_class)
- error_creator(el, is_block, error)
-
- def _add_class(el, class_name):
- if el.get('class'):
- el.set('class', el.get('class')+' '+class_name)
- else:
- el.set('class', class_name)
-
- def _find_elements_for_name(form, name, error):
- if name is None:
- # An error for the entire form
- yield form, error
- return
- if name.startswith('#'):
- # By id
- el = form.get_element_by_id(name[1:])
- if el is not None:
- yield el, error
- return
- els = _name_xpath(form, name=name)
- if not els:
- # FIXME: should this raise an exception?
- return
- if not isinstance(error, (list, tuple)):
- yield els[0], error
- return
- # FIXME: if error is longer than els, should it raise an error?
- for el, err in zip(els, error):
- if err is None:
- continue
- yield el, err
|