lxml.html.formfill

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