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  try:
      6      basestring = __builtins__["basestring"]
      7  except (KeyError, NameError):
      8      # Python 3
      9      basestring = str
     10
     11  __all__ = ['FormNotFound', 'fill_form', 'fill_form_html',
     12             'insert_errors', 'insert_errors_html',
     13             'DefaultErrorCreator']
     14
     15 -class FormNotFound(LookupError):
     16      """
     17      Raised when no form can be found
     18      """
     19
     20  _form_name_xpath = XPath('descendant-or-self::form[name=$name]|descendant-or-self::x:form[name=$name]', namespaces={'x':XHTML_NAMESPACE})
     21  _input_xpath = XPath('|'.join(['descendant-or-self::'+_tag for _tag in ('input','select','textarea','x:input','x:select','x:textarea')]),
     22                                 namespaces={'x':XHTML_NAMESPACE})
     23  _label_for_xpath = XPath('//label[@for=$for_id]|//x:label[@for=$for_id]',
     24                                 namespaces={'x':XHTML_NAMESPACE})
     25  _name_xpath = XPath('descendant-or-self::*[@name=$name]')
     26
     27 -def fill_form(
     28      el,
     29      values,
     30      form_id=None,
     31      form_index=None,
     32      ):
     33      el = _find_form(el, form_id=form_id, form_index=form_index)
     34      _fill_form(el, values)
     35
     36 -def fill_form_html(html, values, form_id=None, form_index=None):
     37      result_type = type(html)
     38      if isinstance(html, basestring):
     39          doc = fromstring(html)
     40      else:
     41          doc = copy.deepcopy(html)
     42      fill_form(doc, values, form_id=form_id, form_index=form_index)
     43      return _transform_result(result_type, doc)
     44
     45 -def _fill_form(el, values):
     46      counts = {}
     47      if hasattr(values, 'mixed'):
     48          # For Paste request parameters
     49          values = values.mixed()
     50      inputs = _input_xpath(el)
     51      for input in inputs:
     52          name = input.get('name')
     53          if not name:
     54              continue
     55          if _takes_multiple(input):
     56              value = values.get(name, [])
     57              if not isinstance(value, (list, tuple)):
     58                  value = [value]
     59              _fill_multiple(input, value)
     60          elif name not in values:
     61              continue
     62          else:
     63              index = counts.get(name, 0)
     64              counts[name] = index + 1
     65              value = values[name]
     66              if isinstance(value, (list, tuple)):
     67                  try:
     68                      value = value[index]
     69                  except IndexError:
     70                      continue
     71              elif index > 0:
     72                  continue
     73              _fill_single(input, value)
     74
     75 -def _takes_multiple(input):
     76      if _nons(input.tag) == 'select' and input.get('multiple'):
     77          # FIXME: multiple="0"?
     78          return True
     79      type = input.get('type', '').lower()
     80      if type in ('radio', 'checkbox'):
     81          return True
     82      return False
     83
     84 -def _fill_multiple(input, value):
     85      type = input.get('type', '').lower()
     86      if type == 'checkbox':
     87          v = input.get('value')
     88          if v is None:
     89              if not value:
     90                  result = False
     91              else:
     92                  result = value[0]
     93                  if isinstance(value, basestring):
     94                      # The only valid "on" value for an unnamed checkbox is 'on'
     95                      result = result == 'on'
     96              _check(input, result)
     97          else:
     98              _check(input, v in value)
     99      elif type == 'radio':
    100          v = input.get('value')
    101          _check(input, v in value)
    102      else:
    103          assert _nons(input.tag) == 'select'
    104          for option in _options_xpath(input):
    105              v = option.get('value')
    106              if v is None:
    107                  # This seems to be the default, at least on IE
    108                  # FIXME: but I'm not sure
    109                  v = option.text_content()
    110              _select(option, v in value)
    111
    112 -def _check(el, check):
    113      if check:
    114          el.set('checked', '')
    115      else:
    116          if 'checked' in el.attrib:
    117              del el.attrib['checked']
    118
    119 -def _select(el, select):
    120      if select:
    121          el.set('selected', '')
    122      else:
    123          if 'selected' in el.attrib:
    124              del el.attrib['selected']
    125
    126 -def _fill_single(input, value):
    127      if _nons(input.tag) == 'textarea':
    128          input.clear()
    129          input.text = value
    130      else:
    131          input.set('value', value)
    132
    133 -def _find_form(el, form_id=None, form_index=None):
    134      if form_id is None and form_index is None:
    135          forms = _forms_xpath(el)
    136          for form in forms:
    137              return form
    138          raise FormNotFound(
    139              "No forms in page")
    140      if form_id is not None:
    141          form = el.get_element_by_id(form_id)
    142          if form is not None:
    143              return form
    144          forms = _form_name_xpath(el, name=form_id)
    145          if forms:
    146              return forms[0]
    147          else:
    148              raise FormNotFound(
    149                  "No form with the name or id of %r (forms: %s)"
    150                  % (id, ', '.join(_find_form_ids(el))))
    151      if form_index is not None:
    152          forms = _forms_xpath(el)
    153          try:
    154              return forms[form_index]
    155          except IndexError:
    156              raise FormNotFound(
    157                  "There is no form with the index %r (%i forms found)"
    158                  % (form_index, len(forms)))
    159
    160 -def _find_form_ids(el):
    161      forms = _forms_xpath(el)
    162      if not forms:
    163          yield '(no forms)'
    164          return
    165      for index, form in enumerate(forms):
    166          if form.get('id'):
    167              if form.get('name'):
    168                  yield '%s or %s' % (form.get('id'),
    169                                       form.get('name'))
    170              else:
    171                  yield form.get('id')
    172          elif form.get('name'):
    173              yield form.get('name')
    174          else:
    175              yield '(unnamed form %s)' % index
    176
    177  ############################################################
    178  ## Error filling
    179  ############################################################
    180
    181 -class DefaultErrorCreator(object):
    182      insert_before = True
    183      block_inside = True
    184      error_container_tag = 'div'
    185      error_message_class = 'error-message'
    186      error_block_class = 'error-block'
    187      default_message = "Invalid"
    188
    189 -    def __init__(self, **kw):
    190          for name, value in kw.items():
    191              if not hasattr(self, name):
    192                  raise TypeError(
    193                      "Unexpected keyword argument: %s" % name)
    194              setattr(self, name, value)
    195
    196 -    def __call__(self, el, is_block, message):
    197          error_el = el.makeelement(self.error_container_tag)
    198          if self.error_message_class:
    199              error_el.set('class', self.error_message_class)
    200          if is_block and self.error_block_class:
    201              error_el.set('class', error_el.get('class', '')+' '+self.error_block_class)
    202          if message is None or message == '':
    203              message = self.default_message
    204          if isinstance(message, ElementBase):
    205              error_el.append(message)
    206          else:
    207              assert isinstance(message, basestring), (
    208                  "Bad message; should be a string or element: %r" % message)
    209              error_el.text = message or self.default_message
    210          if is_block and self.block_inside:
    211              if self.insert_before:
    212                  error_el.tail = el.text
    213                  el.text = None
    214                  el.insert(0, error_el)
    215              else:
    216                  el.append(error_el)
    217          else:
    218              parent = el.getparent()
    219              pos = parent.index(el)
    220              if self.insert_before:
    221                  parent.insert(pos, error_el)
    222              else:
    223                  error_el.tail = el.tail
    224                  el.tail = None
    225                  parent.insert(pos+1, error_el)
    226
    227  default_error_creator = DefaultErrorCreator()
    228
    229
    230 -def insert_errors(
    231      el,
    232      errors,
    233      form_id=None,
    234      form_index=None,
    235      error_class="error",
    236      error_creator=default_error_creator,
    237      ):
    238      el = _find_form(el, form_id=form_id, form_index=form_index)
    239      for name, error in errors.items():
    240          if error is None:
    241              continue
    242          for error_el, message in _find_elements_for_name(el, name, error):
    243              assert isinstance(message, (basestring, type(None), ElementBase)), (
    244                  "Bad message: %r" % message)
    245              _insert_error(error_el, message, error_class, error_creator)
    246
    247 -def insert_errors_html(html, values, **kw):
    248      result_type = type(html)
    249      if isinstance(html, basestring):
    250          doc = fromstring(html)
    251      else:
    252          doc = copy.deepcopy(html)
    253      insert_errors(doc, values, **kw)
    254      return _transform_result(result_type, doc)
    255
    256 -def _insert_error(el, error, error_class, error_creator):
    257      if _nons(el.tag) in defs.empty_tags or _nons(el.tag) == 'textarea':
    258          is_block = False
    259      else:
    260          is_block = True
    261      if _nons(el.tag) != 'form' and error_class:
    262          _add_class(el, error_class)
    263      if el.get('id'):
    264          labels = _label_for_xpath(el, for_id=el.get('id'))
    265          if labels:
    266              for label in labels:
    267                  _add_class(label, error_class)
    268      error_creator(el, is_block, error)
    269
    270 -def _add_class(el, class_name):
    271      if el.get('class'):
    272          el.set('class', el.get('class')+' '+class_name)
    273      else:
    274          el.set('class', class_name)
    275
    276 -def _find_elements_for_name(form, name, error):
    277      if name is None:
    278          # An error for the entire form
    279          yield form, error
    280          return
    281      if name.startswith('#'):
    282          # By id
    283          el = form.get_element_by_id(name[1:])
    284          if el is not None:
    285              yield el, error
    286          return
    287      els = _name_xpath(form, name=name)
    288      if not els:
    289          # FIXME: should this raise an exception?
    290          return
    291      if not isinstance(error, (list, tuple)):
    292          yield els[0], error
    293          return
    294      # FIXME: if error is longer than els, should it raise an error?
    295      for el, err in zip(els, error):
    296          if err is None:
    297              continue
    298          yield el, err
    299