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  try:
      7      basestring = __builtins__["basestring"]
      8  except (KeyError, NameError):
      9      # Python 3
     10      basestring = str
     11
     12  __all__ = ['FormNotFound', 'fill_form', 'fill_form_html',
     13             'insert_errors', 'insert_errors_html',
     14             'DefaultErrorCreator']
     15
     16 -class FormNotFound(LookupError):
     17      """
     18      Raised when no form can be found
     19      """
     20
     21  _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')]),
     23                                 namespaces={'x':XHTML_NAMESPACE})
     24  _label_for_xpath = XPath('//label[@for=$for_id]|//x:label[@for=$for_id]',
     25                                 namespaces={'x':XHTML_NAMESPACE})
     26  _name_xpath = XPath('descendant-or-self::*[@name=$name]')
     27
     28 -def fill_form(
     29      el,
     30      values,
     31      form_id=None,
     32      form_index=None,
     33      ):
     34      el = _find_form(el, form_id=form_id, form_index=form_index)
     35      _fill_form(el, values)
     36
     37 -def fill_form_html(html, values, form_id=None, form_index=None):
     38      result_type = type(html)
     39      if isinstance(html, basestring):
     40          doc = fromstring(html)
     41      else:
     42          doc = copy.deepcopy(html)
     43      fill_form(doc, values, form_id=form_id, form_index=form_index)
     44      return _transform_result(result_type, doc)
     45
     46 -def _fill_form(el, values):
     47      counts = {}
     48      if hasattr(values, 'mixed'):
     49          # For Paste request parameters
     50          values = values.mixed()
     51      inputs = _input_xpath(el)
     52      for input in inputs:
     53          name = input.get('name')
     54          if not name:
     55              continue
     56          if _takes_multiple(input):
     57              value = values.get(name, [])
     58              if not isinstance(value, (list, tuple)):
     59                  value = [value]
     60              _fill_multiple(input, value)
     61          elif name not in values:
     62              continue
     63          else:
     64              index = counts.get(name, 0)
     65              counts[name] = index + 1
     66              value = values[name]
     67              if isinstance(value, (list, tuple)):
     68                  try:
     69                      value = value[index]
     70                  except IndexError:
     71                      continue
     72              elif index > 0:
     73                  continue
     74              _fill_single(input, value)
     75
     76 -def _takes_multiple(input):
     77      if _nons(input.tag) == 'select' and input.get('multiple'):
     78          # FIXME: multiple="0"?
     79          return True
     80      type = input.get('type', '').lower()
     81      if type in ('radio', 'checkbox'):
     82          return True
     83      return False
     84
     85 -def _fill_multiple(input, value):
     86      type = input.get('type', '').lower()
     87      if type == 'checkbox':
     88          v = input.get('value')
     89          if v is None:
     90              if not value:
     91                  result = False
     92              else:
     93                  result = value[0]
     94                  if isinstance(value, basestring):
     95                      # The only valid "on" value for an unnamed checkbox is 'on'
     96                      result = result == 'on'
     97              _check(input, result)
     98          else:
     99              _check(input, v in value)
    100      elif type == 'radio':
    101          v = input.get('value')
    102          _check(input, v in value)
    103      else:
    104          assert _nons(input.tag) == 'select'
    105          for option in _options_xpath(input):
    106              v = option.get('value')
    107              if v is None:
    108                  # This seems to be the default, at least on IE
    109                  # FIXME: but I'm not sure
    110                  v = option.text_content()
    111              _select(option, v in value)
    112
    113 -def _check(el, check):
    114      if check:
    115          el.set('checked', '')
    116      else:
    117          if 'checked' in el.attrib:
    118              del el.attrib['checked']
    119
    120 -def _select(el, select):
    121      if select:
    122          el.set('selected', '')
    123      else:
    124          if 'selected' in el.attrib:
    125              del el.attrib['selected']
    126
    127 -def _fill_single(input, value):
    128      if _nons(input.tag) == 'textarea':
    129          input.clear()
    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